diff --git a/src/Simplex/Messaging/Agent.hs b/src/Simplex/Messaging/Agent.hs index fd7bc7e24..d0cb7d2fa 100644 --- a/src/Simplex/Messaging/Agent.hs +++ b/src/Simplex/Messaging/Agent.hs @@ -699,7 +699,7 @@ startJoinInvitation userId connId enableNtfs cReqUri pqSup = Just (qInfo, (Compatible e2eRcvParams@(CR.E2ERatchetParams v _ rcDHRr kem_)), aVersion@(Compatible connAgentVersion)) -> do g <- asks random let pqSupport = pqSup `CR.pqSupportAnd` versionPQSupport_ connAgentVersion (Just v) - (pk1, pk2, pKem, e2eSndParams) <- liftIO $ CR.generateSndE2EParams g v (CR.replyKEM_ kem_ pqSupport) + (pk1, pk2, pKem, e2eSndParams) <- liftIO $ CR.generateSndE2EParams g v (CR.replyKEM_ v kem_ pqSupport) (_, rcDHRs) <- atomically $ C.generateKeyPair g rcParams <- liftEitherWith cryptoError $ CR.pqX3dhSnd pk1 pk2 pKem e2eRcvParams maxSupported <- asks $ maxVersion . ($ pqSup) . e2eEncryptVRange . config @@ -759,7 +759,7 @@ joinConnSrv c userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSup subMod joinConnSrv c userId connId enableNtfs cReqUri@CRContactUri {} cInfo pqSup subMode srv = compatibleContactUri cReqUri pqSup >>= \case Just (qInfo, vrsn) -> do - (connId', cReq) <- newConnSrv c userId connId enableNtfs SCMInvitation Nothing (CR.joinContactInitialKeys pqSup) subMode srv + (connId', cReq) <- newConnSrv c userId connId enableNtfs SCMInvitation Nothing (CR.IKNoPQ pqSup) subMode srv sendInvitation c userId qInfo vrsn cReq cInfo pure connId' Nothing -> throwError $ AGENT A_VERSION @@ -2368,7 +2368,6 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v, -- show connection request even if invitaion via contact address is not compatible. -- in case invitation not compatible, assume there is no PQ encryption support. pqSupport <- maybe PQSupportOff pqSupported <$> compatibleInvitationUri connReq PQSupportOn - liftIO $ print pqSupport g <- asks random let newInv = NewInvitation {contactConnId = connId, connReq, recipientConnInfo = cInfo} invId <- withStore c $ \db -> createInvitation db g newInv @@ -2554,10 +2553,10 @@ enqueueRatchetKey c cData@ConnData {connId, pqSupport} sq e2eEncryption = do pure internalId -- encoded AgentMessage -> encoded EncAgentMessage -agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (PQSupport -> Int) -> Maybe PQEncryption -> ExceptT StoreError IO (ByteString, PQEncryption) -agentRatchetEncrypt db ConnData {connId, pqSupport} msg getPaddedLen pqEnc_ = do +agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> ExceptT StoreError IO (ByteString, PQEncryption) +agentRatchetEncrypt db ConnData {connId, connAgentVersion = v, pqSupport} msg getPaddedLen pqEnc_ = do rc <- ExceptT $ getRatchet db connId - let paddedLen = getPaddedLen pqSupport + let paddedLen = getPaddedLen v pqSupport (encMsg, rc') <- liftE (SEAgentError . cryptoError) $ CR.rcEncrypt rc paddedLen msg pqEnc_ liftIO $ updateRatchet db connId rc' CR.SMDNoChange pure (encMsg, CR.rcSndKEM rc') diff --git a/src/Simplex/Messaging/Agent/Protocol.hs b/src/Simplex/Messaging/Agent/Protocol.hs index 465b19a01..0c6bfd1ba 100644 --- a/src/Simplex/Messaging/Agent/Protocol.hs +++ b/src/Simplex/Messaging/Agent/Protocol.hs @@ -284,17 +284,17 @@ supportedSMPAgentVRange pq = -- it is shorter to allow all handshake headers, -- including E2E (double-ratchet) parameters and -- signing key of the sender for the server -e2eEncConnInfoLength :: PQSupport -> Int -e2eEncConnInfoLength = \case +e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int +e2eEncConnInfoLength v = \case -- reduced by 3700 (roughly the increase of message ratchet header size + key and ciphertext in reply link) - PQSupportOn -> 11148 - PQSupportOff -> 14848 + PQSupportOn | v >= pqdrSMPAgentVersion -> 11148 + _ -> 14848 -e2eEncUserMsgLength :: PQSupport -> Int -e2eEncUserMsgLength = \case +e2eEncUserMsgLength :: VersionSMPA -> PQSupport -> Int +e2eEncUserMsgLength v = \case -- reduced by 2200 (roughly the increase of message ratchet header size) - PQSupportOn -> 13656 - PQSupportOff -> 15856 + PQSupportOn | v >= pqdrSMPAgentVersion -> 13656 + _ -> 15856 -- | Raw (unparsed) SMP agent protocol transmission. type ARawTransmission = (ByteString, ByteString, ByteString) diff --git a/src/Simplex/Messaging/Crypto/Ratchet.hs b/src/Simplex/Messaging/Crypto/Ratchet.hs index 47ca1c0f2..534fa7c07 100644 --- a/src/Simplex/Messaging/Crypto/Ratchet.hs +++ b/src/Simplex/Messaging/Crypto/Ratchet.hs @@ -54,12 +54,11 @@ module Simplex.Messaging.Crypto.Ratchet generateSndE2EParams, initialPQEncryption, connPQEncryption, - joinContactInitialKeys, replyKEM_, pqSupportToEnc, pqEncToSupport, pqSupportAnd, - pqSupportOrEnc, + pqEnableSupport, pqX3dhSnd, pqX3dhRcv, initSndRatchet, @@ -672,15 +671,15 @@ data MsgHeader a = MsgHeader -- 69 = 2 (original size) + 2 + 1+56 (Curve448) + 4 + 4 -- TODO PQ this must be version-dependent -- TODO this is the exact size, some reserve should be added -paddedHeaderLen :: PQSupport -> Int -paddedHeaderLen = \case - PQSupportOn -> 2288 - PQSupportOff -> 88 +paddedHeaderLen :: VersionE2E -> PQSupport -> Int +paddedHeaderLen v = \case + PQSupportOn | v >= pqRatchetE2EEncryptVersion -> 2288 + _ -> 88 -- only used in tests to validate correct padding -- (2 bytes - version size, 1 byte - header size, not to have it fixed or version-dependent) -fullHeaderLen :: PQSupport -> Int -fullHeaderLen pq = 2 + 1 + paddedHeaderLen pq + authTagSize + ivSize @AES256 +fullHeaderLen :: VersionE2E -> PQSupport -> Int +fullHeaderLen v pq = 2 + 1 + paddedHeaderLen v pq + authTagSize + ivSize @AES256 -- pass the current version, as MsgHeader only includes the max supported version that can be different from the current encodeMsgHeader :: AlgorithmI a => VersionE2E -> MsgHeader a -> ByteString @@ -718,7 +717,8 @@ instance Encoding EncMessageHeader where encodeLarge :: VersionE2E -> ByteString -> ByteString encodeLarge v s -- the condition for length is not necessary, it's here as a fallback. - | v >= pqRatchetE2EEncryptVersion || B.length s > 255 = smpEncode $ Large s + -- | v >= pqRatchetE2EEncryptVersion || B.length s > 255 = smpEncode $ Large s + | v >= pqRatchetE2EEncryptVersion = smpEncode $ Large s | otherwise = smpEncode s -- This parser relies on the fact that header cannot be shorter than 32 bytes (it is ~69 bytes without PQ KEM), @@ -793,15 +793,15 @@ pqEncToSupport (PQEncryption pq) = PQSupport pq pqSupportAnd :: PQSupport -> PQSupport -> PQSupport pqSupportAnd (PQSupport s1) (PQSupport s2) = PQSupport $ s1 && s2 -pqSupportOrEnc :: PQSupport -> PQEncryption -> PQSupport -pqSupportOrEnc (PQSupport sup) (PQEncryption enc) = PQSupport $ sup || enc +pqEnableSupport :: VersionE2E -> PQSupport -> PQEncryption -> PQSupport +pqEnableSupport v (PQSupport sup) (PQEncryption enc) = PQSupport $ sup || (v >= pqRatchetE2EEncryptVersion && enc) -replyKEM_ :: Maybe (RKEMParams 'RKSProposed) -> PQSupport -> Maybe AUseKEM -replyKEM_ kem_ = \case - PQSupportOn -> Just $ case kem_ of +replyKEM_ :: VersionE2E -> Maybe (RKEMParams 'RKSProposed) -> PQSupport -> Maybe AUseKEM +replyKEM_ v kem_ = \case + PQSupportOn | v >= pqRatchetE2EEncryptVersion -> Just $ case kem_ of Just (RKParamsProposed k) -> AUseKEM SRKSAccepted $ AcceptKEM k Nothing -> AUseKEM SRKSProposed ProposeKEM - PQSupportOff -> Nothing + _ -> Nothing instance StrEncoding PQEncryption where strEncode pqMode @@ -848,12 +848,6 @@ connPQEncryption = \case IKUsePQ -> PQSupportOn IKNoPQ pq -> pq -- default for creating connection is IKNoPQ PQEncOn --- determines whether PQ key should be included in invitation link sent to contact address -joinContactInitialKeys :: PQSupport -> InitialKeys -joinContactInitialKeys = \case - PQSupportOn -> IKUsePQ -- default - PQSupportOff -> IKNoPQ PQSupportOff - rcEncrypt :: AlgorithmI a => Ratchet a -> Int -> ByteString -> Maybe PQEncryption -> ExceptT CryptoError IO (ByteString, Ratchet a) rcEncrypt Ratchet {rcSnd = Nothing} _ _ _ = throwE CERatchetState rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs, rcPN, rcAD = Str rcAD, rcSupportKEM, rcEnableKEM, rcVersion} paddedMsgLen msg pqEnc_ = do @@ -863,9 +857,14 @@ rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, -- PQ encryption can be enabled or disabled rcEnableKEM' = fromMaybe rcEnableKEM pqEnc_ -- support for PQ encryption (and therefore large headers/small envelopes) can only be enabled, it cannot be disabled - rcSupportKEM' = rcSupportKEM `pqSupportOrEnc` rcEnableKEM' + rcSupportKEM' = pqEnableSupport v rcSupportKEM rcEnableKEM' + -- This sets max version to support PQ encryption. + -- Current version upgrade happens when peer decrypts the message. + -- TODO v5.7 remove version upgrade here, as it's already upgraded above + maxSupported' = max currentE2EEncryptVersion $ if pqEnc_ == Just PQEncOn then pqRatchetE2EEncryptVersion else v + rcVersion' = rcVersion {maxSupported = maxSupported'} -- enc_header = HENCRYPT(state.HKs, header) - (ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV (paddedHeaderLen rcSupportKEM') rcAD (msgHeader v) + (ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV (paddedHeaderLen v rcSupportKEM') rcAD (msgHeader v maxSupported') -- return enc_header, ENCRYPT(mk, plaintext, CONCAT(AD, enc_header)) let emHeader = smpEncode EncMessageHeader {ehVersion = v, ehBody, ehAuthTag, ehIV} (emAuthTag, emBody) <- encryptAEAD mk iv paddedMsgLen (rcAD <> emHeader) msg @@ -883,16 +882,10 @@ rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs = rcNs + 1, rcSupportKEM = rcSupportKEM', rcEnableKEM = rcEnableKEM', - rcVersion = rcVersion {maxSupported = max v currentE2EEncryptVersion} + rcVersion = rcVersion', + rcKEM = if pqEnc_ == Just PQEncOff then (\rck -> rck {rcKEMs = Nothing}) <$> rcKEM else rcKEM } - rc'' = case pqEnc_ of - Nothing -> rc' - -- This sets max version to support PQ encryption. - -- Current version upgrade happens when peer decrypts the message. - -- TODO v5.7 remove version upgrade here, as it's already upgraded above - Just PQEncOn -> rc' {rcVersion = rcVersion {maxSupported = max v pqRatchetE2EEncryptVersion}} - Just PQEncOff -> rc' {rcKEM = (\rck -> rck {rcKEMs = Nothing}) <$> rcKEM} - pure (msg', rc'') + pure (msg', rc') where -- header = HEADER_PQ2( -- dh = state.DHRs.public, @@ -901,11 +894,11 @@ rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, -- pn = state.PN, -- n = state.Ns -- ) - msgHeader v = + msgHeader v maxSupported' = encodeMsgHeader - v + v MsgHeader - { msgMaxVersion = maxSupported rcVersion, + { msgMaxVersion = maxSupported', msgDHRs = publicKey rcDHRs, msgKEM = msgKEMParams <$> rcKEM, msgPN = rcPN, @@ -982,7 +975,7 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do smkDiff :: SkippedMsgKeys -> SkippedMsgDiff smkDiff smks = if M.null smks then SMDNoChange else SMDAdd smks ratchetStep :: Ratchet a -> MsgHeader a -> ExceptT CryptoError IO (Ratchet a) - ratchetStep rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr, rcSupportKEM} MsgHeader {msgDHRs, msgKEM} = do + ratchetStep rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr, rcSupportKEM, rcVersion = rv} MsgHeader {msgDHRs, msgKEM} = do (kemSS, kemSS', rcKEM') <- pqRatchetStep rc' msgKEM -- state.DHRs = GENERATE_DH() (_, rcDHRs') <- atomically $ generateKeyPair @a g @@ -997,7 +990,7 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do rc' { rcDHRs = rcDHRs', rcKEM = rcKEM', - rcSupportKEM = rcSupportKEM `pqSupportOrEnc` rcEnableKEM', + rcSupportKEM = pqEnableSupport (current rv) rcSupportKEM rcEnableKEM', rcEnableKEM = rcEnableKEM', rcSndKEM = PQEncryption sndKEM, rcRcvKEM = PQEncryption rcvKEM, diff --git a/tests/AgentTests/DoubleRatchetTests.hs b/tests/AgentTests/DoubleRatchetTests.hs index 5b73be893..9490fe2e2 100644 --- a/tests/AgentTests/DoubleRatchetTests.hs +++ b/tests/AgentTests/DoubleRatchetTests.hs @@ -90,15 +90,12 @@ paddedMsgLen :: Int paddedMsgLen = 100 fullMsgLen :: Ratchet a -> Int -fullMsgLen Ratchet {rcSupportKEM} = headerLenLength + fullHeaderLen rcSupportKEM + C.authTagSize + paddedMsgLen +fullMsgLen Ratchet {rcSupportKEM, rcVersion} = headerLenLength + fullHeaderLen v rcSupportKEM + C.authTagSize + paddedMsgLen where - -- v = current rcVersion + v = current rcVersion headerLenLength = case rcSupportKEM of - PQSupportOn -> 3 -- two bytes are added because of two Large used in new encoding - PQSupportOff -> 1 - -- TODO PQ below should work too - -- | v >= pqRatchetE2EEncryptVersion = 3 - -- | otherwise = 1 + PQSupportOn | v >= pqRatchetE2EEncryptVersion -> 3 -- two bytes are added because of two Large used in new encoding + _ -> 1 testMessageHeader :: forall a. AlgorithmI a => VersionE2E -> C.SAlgorithm a -> Expectation testMessageHeader v _ = do @@ -308,10 +305,10 @@ testEnableKEM alice bob _ _ _ = do (alice, "accepting KEM") \#>! bob (alice, "KEM not enabled yet here too") \#>! bob (bob, "KEM is still not enabled") \#>! alice - (alice, "KEM still not enabled 2") \#>! bob - (bob, "now KEM is enabled") !#> alice - (alice, "now KEM is enabled for both sides") !#> bob - (bob, "Still enabled for both sides") !#> alice + (alice, "now KEM is enabled") !#>! bob + (bob, "now KEM is enabled for both sides") !#> alice + (alice, "still enabled for both sides") !#> bob + (bob, "still enabled for both sides 2") !#> alice (alice, "disabling KEM") !#>\ bob (bob, "KEM not disabled yet") !#> alice (alice, "KEM disabled") \#> bob @@ -326,10 +323,10 @@ testEnableKEMStrict alice bob _ _ _ = do (alice, "accepting KEM") \#>! bob (alice, "KEM not enabled yet here too") \#>! bob (bob, "KEM is still not enabled") \#>! alice - (alice, "KEM still not enabled 2") \#>! bob - (bob, "now KEM is enabled") !#>! alice - (alice, "now KEM is enabled for both sides") !#>! bob - (bob, "Still enabled for both sides") !#>! alice + (alice, "now KEM is enabled") !#>! bob + (bob, "now KEM is enabled for both sides") !#>! alice + (alice, "still enabled for both sides") !#>! bob + (bob, "still enabled for both sides 2") !#>! alice (alice, "disabling KEM") !#>\ bob (bob, "KEM not disabled yet") !#>! alice (alice, "KEM disabled") \#>\ bob diff --git a/tests/AgentTests/FunctionalAPITests.hs b/tests/AgentTests/FunctionalAPITests.hs index 424a681e2..95f3a3db2 100644 --- a/tests/AgentTests/FunctionalAPITests.hs +++ b/tests/AgentTests/FunctionalAPITests.hs @@ -164,11 +164,14 @@ pattern Msg :: MsgBody -> ACommand 'Agent e pattern Msg msgBody <- MSG MsgMeta {integrity = MsgOk, pqEncryption = PQEncOn} _ msgBody pattern Msg' :: AgentMsgId -> PQEncryption -> MsgBody -> ACommand 'Agent e -pattern Msg' aMsgId pqEncryption msgBody <- MSG MsgMeta {integrity = MsgOk, recipient = (aMsgId, _), pqEncryption} _ msgBody +pattern Msg' aMsgId pq msgBody <- MSG MsgMeta {integrity = MsgOk, recipient = (aMsgId, _), pqEncryption = pq} _ msgBody pattern MsgErr :: AgentMsgId -> MsgErrorType -> MsgBody -> ACommand 'Agent 'AEConn pattern MsgErr msgId err msgBody <- MSG MsgMeta {recipient = (msgId, _), integrity = MsgError err} _ msgBody +pattern MsgErr' :: AgentMsgId -> MsgErrorType -> PQEncryption -> MsgBody -> ACommand 'Agent 'AEConn +pattern MsgErr' msgId err pq msgBody <- MSG MsgMeta {recipient = (msgId, _), integrity = MsgError err, pqEncryption = pq} _ msgBody + pattern Rcvd :: AgentMsgId -> ACommand 'Agent 'AEConn pattern Rcvd agentMsgId <- RCVD MsgMeta {integrity = MsgOk} [MsgReceipt {agentMsgId, msgRcptStatus = MROk}] @@ -256,6 +259,8 @@ functionalAPITests t = do withSmpServer t testServerMultipleIdentities it "should connect with two peers" $ withSmpServer t testAgentClient3 + it "should establish connection without PQ encryption and enable it" $ + withSmpServer t testEnablePQEncryption describe "Establishing duplex connection v2, different Ratchet versions" $ testRatchetMatrix2 t runAgentClientTest describe "Establish duplex connection via contact address" $ @@ -516,6 +521,76 @@ runAgentClientTest pqSupport alice@AgentClient {} bob baseId = where msgId = subtract baseId . fst +testEnablePQEncryption :: HasCallStack => IO () +testEnablePQEncryption = do + ca <- getSMPAgentClient' 1 agentCfg initAgentServers testDB + cb <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 + g <- C.newRandom + runRight_ $ do + (aId, bId) <- makeConnection_ PQSupportOff ca cb + let a = (ca, aId) + b = (cb, bId) + (a, 4, "msg 1") \#>\ b + (b, 5, "msg 2") \#>\ a + -- 45 bytes is used by agent message envelope inside double ratchet message envelope + let largeMsg g' pqEnc = atomically $ C.randomBytes (e2eEncUserMsgLength pqdrSMPAgentVersion pqEnc - 45) g' + lrg <- largeMsg g PQSupportOff + (a, 6, lrg) \#>\ b + (b, 7, lrg) \#>\ a + -- enabling PQ encryption + (a, 8, lrg) \#>! b + (b, 9, lrg) \#>! a + -- switched to smaller envelopes (before reporting PQ encryption enabled) + sml <- largeMsg g PQSupportOn + -- fail because of message size + Left (A.CMD LARGE) <- tryError $ A.sendMessage ca bId PQEncOn SMP.noMsgFlags lrg + (11, PQEncOff) <- A.sendMessage ca bId PQEncOn SMP.noMsgFlags sml + get ca =##> \case ("", connId, SENT 11) -> connId == bId; _ -> False + get cb =##> \case ("", connId, MsgErr' 10 MsgSkipped {} PQEncOff msg') -> connId == aId && msg' == sml; _ -> False + ackMessage cb aId 10 Nothing + -- -- fail in reply to sync IDss + Left (A.CMD LARGE) <- tryError $ A.sendMessage cb aId PQEncOn SMP.noMsgFlags lrg + (12, PQEncOn) <- A.sendMessage cb aId PQEncOn SMP.noMsgFlags sml + get cb =##> \case ("", connId, SENT 12) -> connId == aId; _ -> False + get ca =##> \case ("", connId, MsgErr' 12 MsgSkipped {} PQEncOn msg') -> connId == bId && msg' == sml; _ -> False + ackMessage ca bId 12 Nothing + -- PQ encryption now enabled + (a, 13, sml) !#>! b + (b, 14, sml) !#>! a + -- disabling PQ encryption + (a, 15, sml) !#>\ b + (b, 16, sml) !#>\ a + (a, 17, sml) \#>\ b + (b, 18, sml) \#>\ a + -- enabling PQ encryption again + (a, 19, sml) \#>! b + (b, 20, sml) \#>! a + (a, 21, sml) \#>! b + (b, 22, sml) !#>! a + (a, 23, sml) !#>! b + -- disabling PQ encryption again + (b, 24, sml) !#>\ a + (a, 25, sml) !#>\ b + (b, 26, sml) \#>\ a + (a, 27, sml) \#>\ b + -- PQ encryption is now disabled, but support remained enabled, so we still cannot send larger messages + Left (A.CMD LARGE) <- tryError $ A.sendMessage ca bId PQEncOff SMP.noMsgFlags (sml <> "123456") + Left (A.CMD LARGE) <- tryError $ A.sendMessage cb aId PQEncOff SMP.noMsgFlags (sml <> "123456") + pure () + where + (\#>\) = PQEncOff `sndRcv` PQEncOff + (\#>!) = PQEncOff `sndRcv` PQEncOn + (!#>!) = PQEncOn `sndRcv` PQEncOn + (!#>\) = PQEncOn `sndRcv` PQEncOff + +sndRcv :: PQEncryption -> PQEncryption -> ((AgentClient, ConnId), AgentMsgId, MsgBody) -> (AgentClient, ConnId) -> ExceptT AgentErrorType IO () +sndRcv pqEnc pqEnc' ((c1, id1), mId, msg) (c2, id2) = do + r <- A.sendMessage c1 id2 pqEnc' SMP.noMsgFlags msg + liftIO $ r `shouldBe` (mId, pqEnc) + get c1 =##> \case ("", connId, SENT mId') -> connId == id2 && mId' == mId; _ -> False + get c2 =##> \case ("", connId, Msg' mId' pq msg') -> connId == id1 && mId' == mId && msg' == msg && pq == pqEnc; _ -> False + ackMessage c2 id1 mId Nothing + testAgentClient3 :: HasCallStack => IO () testAgentClient3 = do a <- getSMPAgentClient' 1 agentCfg initAgentServers testDB