core: update simplexmq (fast connection handshake) (#4369)

* core: update simplexmq (fast connection handshake)

* core: update simplexmq (fast connection handshake)

* fix encoding tests

* fix tests
This commit is contained in:
Evgeny Poberezkin
2024-07-04 07:58:13 +01:00
committed by GitHub
parent 6a273677f1
commit f12e504828
4 changed files with 175 additions and 152 deletions

View File

@@ -36,7 +36,7 @@ import Simplex.Chat.Terminal.Output (newChatTerminal)
import Simplex.Chat.Types
import Simplex.FileTransfer.Description (kb, mb)
import Simplex.FileTransfer.Server (runXFTPServerBlocking)
import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defaultFileExpiration)
import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defaultFileExpiration, supportedXFTPhandshakes)
import Simplex.FileTransfer.Transport (supportedFileServerVRange)
import Simplex.Messaging.Agent (disposeAgentClient)
import Simplex.Messaging.Agent.Env.SQLite
@@ -48,10 +48,11 @@ import Simplex.Messaging.Client (ProtocolClientConfig (..))
import Simplex.Messaging.Client.Agent (defaultSMPClientAgentConfig)
import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange)
import qualified Simplex.Messaging.Crypto.Ratchet as CR
import Simplex.Messaging.Protocol (srvHostnamesSMPClientVersion)
import Simplex.Messaging.Server (runSMPServerBlocking)
import Simplex.Messaging.Server.Env.STM
import Simplex.Messaging.Transport
import Simplex.Messaging.Transport.Server (defaultTransportServerConfig)
import Simplex.Messaging.Transport.Server (TransportServerConfig (..), defaultTransportServerConfig)
import Simplex.Messaging.Version
import Simplex.Messaging.Version.Internal
import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive)
@@ -135,6 +136,14 @@ testAgentCfg =
{ reconnectInterval = (reconnectInterval aCfg) {initialInterval = 50000}
}
testAgentCfgSlow :: AgentConfig
testAgentCfgSlow =
testAgentCfg
{ smpClientVRange = mkVersionRange (Version 1) srvHostnamesSMPClientVersion, -- v2
smpAgentVRange = mkVersionRange duplexHandshakeSMPAgentVersion pqdrSMPAgentVersion, -- v5
smpCfg = (smpCfg testAgentCfg) {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion} -- v8
}
testCfg :: ChatConfig
testCfg =
defaultChatConfig
@@ -144,6 +153,9 @@ testCfg =
tbqSize = 16
}
testCfgSlow :: ChatConfig
testCfgSlow = testCfg {agentConfig = testAgentCfgSlow}
testAgentCfgVPrev :: AgentConfig
testAgentCfgVPrev =
testAgentCfg
@@ -427,11 +439,11 @@ smpServerCfg =
serverStatsLogFile = "tests/smp-server-stats.daily.log",
serverStatsBackupFile = Nothing,
smpServerVRange = supportedServerSMPRelayVRange,
transportConfig = defaultTransportServerConfig,
transportConfig = defaultTransportServerConfig {alpn = Just supportedSMPHandshakes},
smpHandshakeTimeout = 1000000,
controlPort = Nothing,
smpAgentCfg = defaultSMPClientAgentConfig,
allowSMPProxy = False,
allowSMPProxy = True,
serverClientConcurrency = 16,
information = Nothing
}
@@ -473,7 +485,7 @@ xftpServerConfig =
serverStatsLogFile = "tests/tmp/xftp-server-stats.daily.log",
serverStatsBackupFile = Nothing,
controlPort = Nothing,
transportConfig = defaultTransportServerConfig,
transportConfig = defaultTransportServerConfig {alpn = Just supportedXFTPhandshakes},
responseDelay = 0
}

View File

@@ -63,11 +63,22 @@ chatDirectTests = do
it "get and set XFTP servers" testGetSetXFTPServers
it "test XFTP server connection" testTestXFTPServer
describe "async connection handshake" $ do
it "connect when initiating client goes offline" testAsyncInitiatingOffline
it "connect when accepting client goes offline" testAsyncAcceptingOffline
describe "connect when initiating client goes offline" $ do
it "curr" $ testAsyncInitiatingOffline testCfg testCfg
it "v5" $ testAsyncInitiatingOffline testCfgSlow testCfgSlow
it "v5/curr" $ testAsyncInitiatingOffline testCfgSlow testCfg
it "curr/v5" $ testAsyncInitiatingOffline testCfg testCfgSlow
describe "connect when accepting client goes offline" $ do
it "curr" $ testAsyncAcceptingOffline testCfg testCfg
it "v5" $ testAsyncAcceptingOffline testCfgSlow testCfgSlow
it "v5/curr" $ testAsyncAcceptingOffline testCfgSlow testCfg
it "curr/v5" $ testAsyncAcceptingOffline testCfg testCfgSlow
describe "connect, fully asynchronous (when clients are never simultaneously online)" $ do
it "curr" testFullAsyncFast
-- fails in CI
xit'' "v2" testFullAsync
xit'' "v5" $ testFullAsyncSlow testCfgSlow testCfgSlow
xit'' "v5/curr" $ testFullAsyncSlow testCfgSlow testCfg
xit'' "curr/v5" $ testFullAsyncSlow testCfg testCfgSlow
describe "webrtc calls api" $ do
it "negotiate call" testNegotiateCall
describe "maintenance mode" $ do
@@ -842,41 +853,38 @@ testTestXFTPServer =
alice <## "XFTP server test failed at Connect, error: BROKER {brokerAddress = \"xftp://LcJU@localhost:7002\", brokerErr = NETWORK}"
alice <## "Possibly, certificate fingerprint in XFTP server address is incorrect"
testAsyncInitiatingOffline :: HasCallStack => FilePath -> IO ()
testAsyncInitiatingOffline tmp = do
putStrLn "testAsyncInitiatingOffline"
inv <- withNewTestChat tmp "alice" aliceProfile $ \alice -> do
testAsyncInitiatingOffline :: HasCallStack => ChatConfig -> ChatConfig -> FilePath -> IO ()
testAsyncInitiatingOffline aliceCfg bobCfg tmp = do
inv <- withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice -> do
threadDelay 250000
alice ##> "/c"
getInvitation alice
withNewTestChat tmp "bob" bobProfile $ \bob -> do
withNewTestChatCfg tmp bobCfg "bob" bobProfile $ \bob -> do
threadDelay 250000
bob ##> ("/c " <> inv)
bob <## "confirmation sent!"
withTestChat tmp "alice" $ \alice -> do
withTestChatCfg tmp aliceCfg "alice" $ \alice -> do
concurrently_
(bob <## "alice (Alice): contact is connected")
(alice <## "bob (Bob): contact is connected")
testAsyncAcceptingOffline :: HasCallStack => FilePath -> IO ()
testAsyncAcceptingOffline tmp = do
putStrLn "testAsyncAcceptingOffline"
inv <- withNewTestChat tmp "alice" aliceProfile $ \alice -> do
testAsyncAcceptingOffline :: HasCallStack => ChatConfig -> ChatConfig -> FilePath -> IO ()
testAsyncAcceptingOffline aliceCfg bobCfg tmp = do
inv <- withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice -> do
alice ##> "/c"
getInvitation alice
withNewTestChat tmp "bob" bobProfile $ \bob -> do
withNewTestChatCfg tmp bobCfg "bob" bobProfile $ \bob -> do
threadDelay 250000
bob ##> ("/c " <> inv)
bob <## "confirmation sent!"
withTestChat tmp "alice" $ \alice -> do
withTestChat tmp "bob" $ \bob -> do
withTestChatCfg tmp aliceCfg "alice" $ \alice -> do
withTestChatCfg tmp bobCfg "bob" $ \bob -> do
concurrently_
(bob <## "alice (Alice): contact is connected")
(alice <## "bob (Bob): contact is connected")
testFullAsync :: HasCallStack => FilePath -> IO ()
testFullAsync tmp = do
putStrLn "testFullAsync"
testFullAsyncFast :: HasCallStack => FilePath -> IO ()
testFullAsyncFast tmp = do
inv <- withNewTestChat tmp "alice" aliceProfile $ \alice -> do
threadDelay 250000
alice ##> "/c"
@@ -885,143 +893,33 @@ testFullAsync tmp = do
threadDelay 250000
bob ##> ("/c " <> inv)
bob <## "confirmation sent!"
withTestChat tmp "alice" $ \_ -> pure () -- connecting... notification in UI
withTestChat tmp "bob" $ \_ -> pure () -- connecting... notification in UI
withTestChat tmp "alice" $ \alice -> do
alice <## "1 contacts connected (use /cs for the list)"
threadDelay 250000
withTestChat tmp "alice" $ \alice ->
alice <## "bob (Bob): contact is connected"
withTestChat tmp "bob" $ \bob -> do
bob <## "1 contacts connected (use /cs for the list)"
withTestChat tmp "bob" $ \bob ->
bob <## "alice (Alice): contact is connected"
testFullAsyncV1 :: HasCallStack => FilePath -> IO ()
testFullAsyncV1 tmp = do
putStrLn "testFullAsyncV1"
inv <- withNewAlice $ \alice -> do
putStrLn "1"
testFullAsyncSlow :: HasCallStack => ChatConfig -> ChatConfig -> FilePath -> IO ()
testFullAsyncSlow aliceCfg bobCfg tmp = do
inv <- withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice -> do
threadDelay 250000
alice ##> "/c"
putStrLn "2"
getInvitation alice
putStrLn "3"
withNewBob $ \bob -> do
putStrLn "4"
withNewTestChatCfg tmp bobCfg "bob" bobProfile $ \bob -> do
threadDelay 250000
bob ##> ("/c " <> inv)
putStrLn "5"
bob <## "confirmation sent!"
putStrLn "6"
withAlice $ \_ -> pure ()
putStrLn "7"
withBob $ \_ -> pure ()
putStrLn "8"
withAlice $ \_ -> pure () -- connecting... notification in UI
withBob $ \_ -> pure () -- connecting... notification in UI
withAlice $ \alice -> do
putStrLn "9"
alice <## "1 contacts connected (use /cs for the list)"
putStrLn "10"
withBob $ \_ -> pure ()
putStrLn "11"
withAlice $ \alice -> do
putStrLn "12"
alice <## "1 contacts connected (use /cs for the list)"
putStrLn "13"
alice <## "bob (Bob): contact is connected"
putStrLn "14"
withBob $ \bob -> do
putStrLn "15"
bob <## "1 contacts connected (use /cs for the list)"
putStrLn "16"
bob <## "alice (Alice): contact is connected"
where
withNewAlice = withNewTestChatV1 tmp "alice" aliceProfile
withAlice = withTestChatV1 tmp "alice"
withNewBob = withNewTestChatV1 tmp "bob" bobProfile
withBob = withTestChatV1 tmp "bob"
testFullAsyncV1toV2 :: HasCallStack => FilePath -> IO ()
testFullAsyncV1toV2 tmp = do
putStrLn "testFullAsyncV1toV2"
inv <- withNewAlice $ \alice -> do
putStrLn "1"
alice ##> "/c"
putStrLn "2"
getInvitation alice
putStrLn "3"
withNewBob $ \bob -> do
putStrLn "4"
bob ##> ("/c " <> inv)
putStrLn "5"
bob <## "confirmation sent!"
withAlice $ \_ -> pure ()
putStrLn "6"
withBob $ \_ -> pure ()
putStrLn "7"
withAlice $ \alice -> do
putStrLn "8"
alice <## "1 contacts connected (use /cs for the list)"
putStrLn "9"
withBob $ \_ -> pure ()
putStrLn "10"
withAlice $ \alice -> do
putStrLn "11"
alice <## "1 contacts connected (use /cs for the list)"
putStrLn "12"
alice <## "bob (Bob): contact is connected"
putStrLn "13"
withBob $ \bob -> do
putStrLn "14"
bob <## "1 contacts connected (use /cs for the list)"
putStrLn "15"
bob <## "alice (Alice): contact is connected"
where
withNewAlice = withNewTestChat tmp "alice" aliceProfile
withAlice = withTestChat tmp "alice"
withNewBob = withNewTestChatV1 tmp "bob" bobProfile
withBob = withTestChatV1 tmp "bob"
testFullAsyncV2toV1 :: HasCallStack => FilePath -> IO ()
testFullAsyncV2toV1 tmp = do
putStrLn "testFullAsyncV2toV1"
inv <- withNewAlice $ \alice -> do
putStrLn "1"
alice ##> "/c"
putStrLn "2"
getInvitation alice
putStrLn "3"
withNewBob $ \bob -> do
putStrLn "4"
bob ##> ("/c " <> inv)
putStrLn "5"
bob <## "confirmation sent!"
putStrLn "6"
withAlice $ \_ -> pure ()
putStrLn "7"
withBob $ \_ -> pure ()
putStrLn "8"
withAlice $ \alice -> do
putStrLn "9"
alice <## "1 contacts connected (use /cs for the list)"
putStrLn "10"
withBob $ \_ -> pure ()
putStrLn "11"
withAlice $ \alice -> do
putStrLn "12"
alice <## "1 contacts connected (use /cs for the list)"
putStrLn "13"
alice <## "bob (Bob): contact is connected"
putStrLn "14"
withBob $ \bob -> do
putStrLn "15"
bob <## "1 contacts connected (use /cs for the list)"
putStrLn "16"
bob <## "alice (Alice): contact is connected"
where
withNewAlice = withNewTestChatV1 tmp "alice" aliceProfile
{-# INLINE withNewAlice #-}
withAlice = withTestChatV1 tmp "alice"
{-# INLINE withAlice #-}
withNewBob = withNewTestChat tmp "bob" bobProfile
{-# INLINE withNewBob #-}
withBob = withTestChat tmp "bob"
{-# INLINE withBob #-}
withAlice = withTestChatCfg tmp aliceCfg "alice"
withBob = withTestChatCfg tmp aliceCfg "bob"
testCallType :: CallType
testCallType = CallType {media = CMVideo, capabilities = CallCapabilities {encryption = True}}
@@ -2463,7 +2361,7 @@ testMsgDecryptError tmp =
withTestChat tmp "bob" $ \bob -> do
bob <## "1 contacts connected (use /cs for the list)"
alice #> "@bob hello again"
bob <# "alice> skipped message ID 10..12"
bob <# "alice> skipped message ID 9..11"
bob <# "alice> hello again"
bob #> "@alice received!"
alice <# "bob> received!"

View File

@@ -90,6 +90,7 @@ chatGroupTests = do
describe "group links without contact connection plan" $ do
it "group link without contact - known group" testPlanGroupLinkNoContactKnown
it "group link without contact - connecting" testPlanGroupLinkNoContactConnecting
it "group link without contact - connecting (slow handshake)" testPlanGroupLinkNoContactConnectingSlow
describe "group message errors" $ do
it "show message decryption error" testGroupMsgDecryptError
it "should report ratchet de-synchronization, synchronize ratchets" testGroupSyncRatchet
@@ -2527,7 +2528,7 @@ testPlanHostContactDeletedGroupLinkKnown =
testPlanGroupLinkOwn :: HasCallStack => FilePath -> IO ()
testPlanGroupLinkOwn tmp =
withNewTestChatCfg tmp testCfgGroupLinkViaContact "alice" aliceProfile $ \alice -> do
withNewTestChatCfg tmp (mkCfgGroupLinkViaContact testCfgSlow) "alice" aliceProfile $ \alice -> do
threadDelay 100000
alice ##> "/g team"
alice <## "group #team is created"
@@ -2630,7 +2631,7 @@ testPlanGroupLinkConnecting tmp = do
bob ##> ("/c " <> gLink)
bob <## "group link: connecting"
where
cfg = testCfgGroupLinkViaContact
cfg = mkCfgGroupLinkViaContact testCfgSlow
testPlanGroupLinkLeaveRejoin :: HasCallStack => FilePath -> IO ()
testPlanGroupLinkLeaveRejoin =
@@ -3228,6 +3229,52 @@ testPlanGroupLinkNoContactConnecting tmp = do
withTestChat tmp "bob" $ \bob -> do
threadDelay 500000
bob <## "#team: joining the group..."
bob <## "#team: you joined the group"
bob ##> ("/_connect plan 1 " <> gLink)
bob <## "group link: known group #team"
bob <## "use #team <message> to send messages"
let gLinkSchema2 = linkAnotherSchema gLink
bob ##> ("/_connect plan 1 " <> gLinkSchema2)
bob <## "group link: known group #team"
bob <## "use #team <message> to send messages"
bob ##> ("/c " <> gLink)
bob <## "group link: known group #team"
bob <## "use #team <message> to send messages"
testPlanGroupLinkNoContactConnectingSlow :: HasCallStack => FilePath -> IO ()
testPlanGroupLinkNoContactConnectingSlow tmp = do
gLink <- withNewTestChatCfg tmp testCfgSlow "alice" aliceProfile $ \alice -> do
alice ##> "/g team"
alice <## "group #team is created"
alice <## "to add members use /a team <name> or /create link #team"
alice ##> "/create link #team"
getGroupLink alice "team" GRMember True
withNewTestChatCfg tmp testCfgSlow "bob" bobProfile $ \bob -> do
threadDelay 100000
bob ##> ("/c " <> gLink)
bob <## "connection request sent!"
bob ##> ("/_connect plan 1 " <> gLink)
bob <## "group link: connecting, allowed to reconnect"
let gLinkSchema2 = linkAnotherSchema gLink
bob ##> ("/_connect plan 1 " <> gLinkSchema2)
bob <## "group link: connecting, allowed to reconnect"
threadDelay 100000
withTestChatCfg tmp testCfgSlow "alice" $ \alice -> do
alice
<### [ "1 group links active",
"#team: group is empty",
"bob (Bob): accepting request to join group #team..."
]
withTestChatCfg tmp testCfgSlow "bob" $ \bob -> do
threadDelay 500000
bob <## "#team: joining the group..."
bob ##> ("/_connect plan 1 " <> gLink)
bob <## "group link: connecting to group #team"
@@ -3253,7 +3300,7 @@ testGroupMsgDecryptError tmp =
bob <## "1 contacts connected (use /cs for the list)"
bob <## "#team: connected to server(s)"
alice #> "#team hello again"
bob <# "#team alice> skipped message ID 10..12"
bob <# "#team alice> skipped message ID 9..11"
bob <# "#team alice> hello again"
bob #> "#team received!"
alice <# "#team bob> received!"

View File

@@ -17,8 +17,8 @@ import Simplex.Chat.Store.Shared (createContact)
import Simplex.Chat.Types (ConnStatus (..), Profile (..))
import Simplex.Chat.Types.Shared (GroupMemberRole (..))
import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Util (encodeJSON)
import Simplex.Messaging.Encoding.String (StrEncoding (..))
import Simplex.Messaging.Util (encodeJSON)
import System.Directory (copyFile, createDirectoryIfMissing)
import Test.Hspec hiding (it)
@@ -42,6 +42,7 @@ chatProfileTests = do
it "contact address ok to connect; known contact" testPlanAddressOkKnown
it "own contact address" testPlanAddressOwn
it "connecting via contact address" testPlanAddressConnecting
it "connecting via contact address (slow handshake)" testPlanAddressConnectingSlow
it "re-connect with deleted contact" testPlanAddressContactDeletedReconnected
it "contact via address" testPlanAddressContactViaAddress
describe "incognito" $ do
@@ -51,6 +52,7 @@ chatProfileTests = do
it "set connection incognito" testSetConnectionIncognito
it "reset connection incognito" testResetConnectionIncognito
it "set connection incognito prohibited during negotiation" testSetConnectionIncognitoProhibitedDuringNegotiation
it "set connection incognito prohibited during negotiation (slow handshake)" testSetConnectionIncognitoProhibitedDuringNegotiationSlow
it "connection incognito unchanged errors" testConnectionIncognitoUnchangedErrors
it "set, reset, set connection incognito" testSetResetSetConnectionIncognito
it "join group incognito" testJoinGroupIncognito
@@ -705,6 +707,49 @@ testPlanAddressConnecting tmp = do
alice ##> "/ac bob"
alice <## "bob (Bob): accepting contact request..."
withTestChat tmp "bob" $ \bob -> do
threadDelay 500000
bob <## "alice (Alice): contact is connected"
bob @@@ [("@alice", "Audio/video calls: enabled")]
bob ##> ("/_connect plan 1 " <> cLink)
bob <## "contact address: known contact alice"
bob <## "use @alice <message> to send messages"
let cLinkSchema2 = linkAnotherSchema cLink
bob ##> ("/_connect plan 1 " <> cLinkSchema2)
bob <## "contact address: known contact alice"
bob <## "use @alice <message> to send messages"
bob ##> ("/c " <> cLink)
bob <## "contact address: known contact alice"
bob <## "use @alice <message> to send messages"
testPlanAddressConnectingSlow :: HasCallStack => FilePath -> IO ()
testPlanAddressConnectingSlow tmp = do
cLink <- withNewTestChatCfg tmp testCfgSlow "alice" aliceProfile $ \alice -> do
alice ##> "/ad"
getContactLink alice True
withNewTestChatCfg tmp testCfgSlow "bob" bobProfile $ \bob -> do
threadDelay 100000
bob ##> ("/c " <> cLink)
bob <## "connection request sent!"
bob ##> ("/_connect plan 1 " <> cLink)
bob <## "contact address: connecting, allowed to reconnect"
let cLinkSchema2 = linkAnotherSchema cLink
bob ##> ("/_connect plan 1 " <> cLinkSchema2)
bob <## "contact address: connecting, allowed to reconnect"
threadDelay 100000
withTestChatCfg tmp testCfgSlow "alice" $ \alice -> do
alice <## "Your address is active! To show: /sa"
alice <## "bob (Bob) wants to connect to you!"
alice <## "to accept: /ac bob"
alice <## "to reject: /rc bob (the sender will NOT be notified)"
alice ##> "/ac bob"
alice <## "bob (Bob): accepting contact request..."
withTestChatCfg tmp testCfgSlow "bob" $ \bob -> do
threadDelay 500000
bob @@@ [("@alice", "")]
bob ##> ("/_connect plan 1 " <> cLink)
@@ -1050,9 +1095,30 @@ testSetConnectionIncognitoProhibitedDuringNegotiation tmp = do
bob <## "confirmation sent!"
withTestChat tmp "alice" $ \alice -> do
threadDelay 250000
alice <## "bob (Bob): contact is connected"
alice ##> "/_set incognito :1 on"
alice <## "chat db error: SEPendingConnectionNotFound {connId = 1}"
withTestChat tmp "bob" $ \bob -> do
bob <## "alice (Alice): contact is connected"
alice <##> bob
alice `hasContactProfiles` ["alice", "bob"]
bob `hasContactProfiles` ["alice", "bob"]
testSetConnectionIncognitoProhibitedDuringNegotiationSlow :: HasCallStack => FilePath -> IO ()
testSetConnectionIncognitoProhibitedDuringNegotiationSlow tmp = do
inv <- withNewTestChatCfg tmp testCfgSlow "alice" aliceProfile $ \alice -> do
threadDelay 250000
alice ##> "/connect"
getInvitation alice
withNewTestChatCfg tmp testCfgSlow "bob" bobProfile $ \bob -> do
threadDelay 250000
bob ##> ("/c " <> inv)
bob <## "confirmation sent!"
withTestChatCfg tmp testCfgSlow "alice" $ \alice -> do
threadDelay 250000
alice ##> "/_set incognito :1 on"
alice <## "chat db error: SEPendingConnectionNotFound {connId = 1}"
withTestChatCfg tmp testCfgSlow "bob" $ \bob -> do
concurrently_
(bob <## "alice (Alice): contact is connected")
(alice <## "bob (Bob): contact is connected")