mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-03-30 20:45:52 +00:00
* agent: support client notices * improve * fix, test * rename * cleanup * send and process notices in more cases * dont delete * dont remove notice on other permanent errors * dont remove notice if there is no notice ID in queue * add server to error * allow deleting * only use notice if key hash matches
1487 lines
76 KiB
Haskell
1487 lines
76 KiB
Haskell
{-# LANGUAGE CPP #-}
|
|
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE GADTs #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedLists #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE PatternSynonyms #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
{-# LANGUAGE TupleSections #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
|
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
|
|
|
module ServerTests where
|
|
|
|
import Control.Concurrent (ThreadId, killThread, threadDelay)
|
|
import Control.Concurrent.Async (concurrently_)
|
|
import Control.Concurrent.STM
|
|
import Control.Exception (SomeException, throwIO, try)
|
|
import Control.Monad
|
|
import Control.Monad.IO.Class
|
|
import CoreTests.MsgStoreTests (testJournalStoreCfg)
|
|
import Data.Bifunctor (first)
|
|
import qualified Data.ByteString.Base64 as B64
|
|
import Data.ByteString.Char8 (ByteString)
|
|
import qualified Data.ByteString.Char8 as B
|
|
import Data.Hashable (hash)
|
|
import qualified Data.IntSet as IS
|
|
import Data.List.NonEmpty (NonEmpty)
|
|
import Data.String (IsString (..))
|
|
import Data.Type.Equality
|
|
import qualified Data.X509.Validation as XV
|
|
import GHC.Stack (withFrozenCallStack)
|
|
import SMPClient
|
|
import qualified Simplex.Messaging.Crypto as C
|
|
import Simplex.Messaging.Encoding
|
|
import Simplex.Messaging.Encoding.String
|
|
import Simplex.Messaging.Parsers (parseAll, parseString)
|
|
import Simplex.Messaging.Protocol
|
|
import Simplex.Messaging.Server (exportMessages)
|
|
import Simplex.Messaging.Server.Env.STM (AStoreType (..), MsgStore (..), ServerConfig (..), ServerStoreCfg (..), readWriteQueueStore)
|
|
import Simplex.Messaging.Server.Expiration
|
|
import Simplex.Messaging.Server.MsgStore.Journal (JournalStoreConfig (..), QStoreCfg (..), stmQueueStore)
|
|
import Simplex.Messaging.Server.MsgStore.Types (MsgStoreClass (..), QSType (..), SMSType (..), SQSType (..), newMsgStore)
|
|
import Simplex.Messaging.Server.Stats (PeriodStatsData (..), ServerStatsData (..))
|
|
import Simplex.Messaging.Server.StoreLog (StoreLogRecord (..), closeStoreLog)
|
|
import Simplex.Messaging.Transport
|
|
import Simplex.Messaging.Util (whenM)
|
|
import Simplex.Messaging.Version (mkVersionRange)
|
|
import System.Directory (doesDirectoryExist, doesFileExist, removeDirectoryRecursive, removeFile)
|
|
import System.IO (IOMode (..), withFile)
|
|
import System.TimeIt (timeItT)
|
|
import System.Timeout
|
|
import Test.HUnit
|
|
import Test.Hspec hiding (fit, it)
|
|
import Util
|
|
|
|
#if defined(dbServerPostgres)
|
|
import CoreTests.MsgStoreTests (testPostgresStoreConfig)
|
|
import Simplex.Messaging.Server.MsgStore.Postgres (PostgresMsgStoreCfg (..), exportDbMessages)
|
|
#endif
|
|
|
|
serverTests :: SpecWith (ASrvTransport, AStoreType)
|
|
serverTests = do
|
|
describe "SMP queues" $ do
|
|
describe "NEW and KEY commands, SEND messages" testCreateSecure
|
|
describe "NEW and SKEY commands" $ do
|
|
testCreateSndSecure
|
|
testSndSecureProhibited
|
|
describe "RKEY command to add additional recipient keys" $
|
|
testCreateUpdateKeys
|
|
describe "NEW, OFF and DEL commands, SEND messages" testCreateDelete
|
|
describe "Stress test" stressTest
|
|
describe "allowNewQueues setting" testAllowNewQueues
|
|
describe "SMP messages" $ do
|
|
describe "duplex communication over 2 SMP connections" testDuplex
|
|
describe "switch subscription to another TCP connection" testSwitchSub
|
|
describe "GET command" testGetCommand
|
|
describe "GET & SUB commands" testGetSubCommands
|
|
describe "Exceeding queue quota" testExceedQueueQuota
|
|
describe "Concurrent sending and delivery" testConcurrentSendDelivery
|
|
describe "Store log" testWithStoreLog
|
|
describe "Restore messages" testRestoreMessages
|
|
describe "Restore messages (old / v2)" testRestoreExpireMessages
|
|
describe "Save prometheus metrics" testPrometheusMetrics
|
|
describe "Timing of AUTH error" testTiming
|
|
describe "Message notifications" $ do
|
|
testMessageNotifications
|
|
testMessageServiceNotifications
|
|
testServiceNotificationsTwoRestarts
|
|
describe "Message expiration" $ do
|
|
testMsgExpireOnSend
|
|
testMsgExpireOnInterval
|
|
testMsgNOTExpireOnInterval
|
|
describe "Blocking queues" $ testBlockMessageQueue
|
|
describe "Short links" $ do
|
|
testInvQueueLinkData
|
|
testContactQueueLinkData
|
|
|
|
pattern Resp :: CorrId -> QueueId -> BrokerMsg -> Transmission (Either ErrorType BrokerMsg)
|
|
pattern Resp corrId queueId command <- (corrId, queueId, Right command)
|
|
|
|
pattern New :: RcvPublicAuthKey -> RcvPublicDhKey -> Command 'Creator
|
|
pattern New rPub dhPub = NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just (QRMessaging Nothing)) Nothing)
|
|
|
|
pattern Ids :: RecipientId -> SenderId -> RcvPublicDhKey -> BrokerMsg
|
|
pattern Ids rId sId srvDh <- IDS (QIK rId sId srvDh _sndSecure _linkId Nothing Nothing)
|
|
|
|
pattern Msg :: MsgId -> MsgBody -> BrokerMsg
|
|
pattern Msg msgId body <- MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body}
|
|
|
|
sendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> (Maybe TAuthorizations, ByteString, EntityId, Command p) -> IO (Transmission (Either ErrorType BrokerMsg))
|
|
sendRecv h@THandle {params} (sgn, corrId, qId, cmd) = do
|
|
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
|
Right () <- tPut1 h (sgn, tToSend)
|
|
tGet1 h
|
|
|
|
signSendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> (ByteString, EntityId, Command p) -> IO (Transmission (Either ErrorType BrokerMsg))
|
|
signSendRecv h pk t = do
|
|
[r] <- signSendRecv_ h pk Nothing t
|
|
pure r
|
|
|
|
signSendRecv2 :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> (ByteString, EntityId, Command p) -> IO (Transmission (Either ErrorType BrokerMsg), Transmission (Either ErrorType BrokerMsg))
|
|
signSendRecv2 h pk t = do
|
|
[r1, r2] <- signSendRecv_ h pk Nothing t
|
|
pure (r1, r2)
|
|
|
|
serviceSignSendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO (Transmission (Either ErrorType BrokerMsg))
|
|
serviceSignSendRecv h pk serviceKey t = do
|
|
[r] <- signSendRecv_ h pk (Just serviceKey) t
|
|
pure r
|
|
|
|
signSendRecv_ :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> Maybe C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO (NonEmpty (Transmission (Either ErrorType BrokerMsg)))
|
|
signSendRecv_ h@THandle {params} (C.APrivateAuthKey a pk) serviceKey_ (corrId, qId, cmd) = do
|
|
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
|
Right () <- tPut1 h (authorize tForAuth, tToSend)
|
|
liftIO $ tGetClient h
|
|
where
|
|
authorize t = (,(`C.sign'` t) <$> serviceKey_) <$> case a of
|
|
C.SEd25519 -> Just . TASignature . C.ASignature C.SEd25519 $ C.sign' pk t'
|
|
C.SEd448 -> Just . TASignature . C.ASignature C.SEd448 $ C.sign' pk t'
|
|
C.SX25519 -> (\THAuthClient {peerServerPubKey = k} -> TAAuthenticator $ C.cbAuthenticate k pk (C.cbNonce corrId) t') <$> thAuth params
|
|
#if !MIN_VERSION_base(4,18,0)
|
|
_sx448 -> undefined -- ghc8107 fails to the branch excluded by types
|
|
#endif
|
|
where
|
|
t' = case (serviceKey_, thAuth params >>= clientService) of
|
|
(Just _, Just THClientService {serviceCertHash = XV.Fingerprint fp}) -> fp <> t
|
|
_ -> t
|
|
|
|
tPut1 :: Transport c => THandle v c 'TClient -> SentRawTransmission -> IO (Either TransportError ())
|
|
tPut1 h t = do
|
|
[r] <- tPut h [Right t]
|
|
pure r
|
|
|
|
tGet1 :: (ProtocolEncoding v err cmd, Transport c) => THandle v c 'TClient -> IO (Transmission (Either err cmd))
|
|
tGet1 h = do
|
|
[r] <- liftIO $ tGetClient h
|
|
pure r
|
|
|
|
(#==) :: (HasCallStack, Eq a, Show a) => (a, a) -> String -> Assertion
|
|
(actual, expected) #== message = assertEqual message expected actual
|
|
|
|
_SEND :: MsgBody -> Command 'Sender
|
|
_SEND = SEND noMsgFlags
|
|
|
|
_SEND' :: MsgBody -> Command 'Sender
|
|
_SEND' = SEND MsgFlags {notification = True}
|
|
|
|
decryptMsgV2 :: C.DhSecret 'C.X25519 -> ByteString -> ByteString -> Either C.CryptoError ByteString
|
|
decryptMsgV2 dhShared = C.cbDecrypt dhShared . C.cbNonce
|
|
|
|
decryptMsgV3 :: C.DhSecret 'C.X25519 -> ByteString -> ByteString -> Either String MsgBody
|
|
decryptMsgV3 dhShared nonce body =
|
|
case parseAll clientRcvMsgBodyP =<< first show (C.cbDecrypt dhShared (C.cbNonce nonce) body) of
|
|
Right ClientRcvMsgBody {msgBody} -> Right msgBody
|
|
Right ClientRcvMsgQuota {} -> Left "ClientRcvMsgQuota"
|
|
Left e -> Left e
|
|
|
|
testCreateSecure :: SpecWith (ASrvTransport, AStoreType)
|
|
testCreateSecure =
|
|
it "should create (NEW) and secure (KEY) queue" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \r s -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv r rKey ("abcd", NoEntity, New rPub dhPub)
|
|
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
|
(rId1, NoEntity) #== "creates queue"
|
|
|
|
Resp "bcda" sId1 ok1 <- sendRecv s ("", "bcda", sId, _SEND "hello")
|
|
(ok1, OK) #== "accepts unsigned SEND"
|
|
(sId1, sId) #== "same queue ID in response 1"
|
|
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 r
|
|
(dec mId1 msg1, Right "hello") #== "delivers message"
|
|
|
|
Resp "cdab" _ ok4 <- signSendRecv r rKey ("cdab", rId, ACK mId1)
|
|
(ok4, OK) #== "replies OK when message acknowledged if no more messages"
|
|
|
|
Resp "dabc" _ err6 <- signSendRecv r rKey ("dabc", rId, ACK mId1)
|
|
(err6, ERR NO_MSG) #== "replies ERR when message acknowledged without messages"
|
|
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "abcd" sId2 err1 <- signSendRecv s sKey ("abcd", sId, _SEND "hello")
|
|
(err1, ERR AUTH) #== "rejects signed SEND"
|
|
(sId2, sId) #== "same queue ID in response 2"
|
|
|
|
Resp "bcda" _ err2 <- sendRecv r (sampleSig, "bcda", rId, KEY sPub)
|
|
(err2, ERR AUTH) #== "rejects KEY with wrong signature"
|
|
|
|
Resp "cdab" _ err3 <- signSendRecv r rKey ("cdab", sId, KEY sPub)
|
|
(err3, ERR AUTH) #== "rejects KEY with sender's ID"
|
|
|
|
Resp "dabc" rId2 ok2 <- signSendRecv r rKey ("dabc", rId, KEY sPub)
|
|
(ok2, OK) #== "secures queue"
|
|
(rId2, rId) #== "same queue ID in response 3"
|
|
|
|
Resp "abcd" _ OK <- signSendRecv r rKey ("abcd", rId, KEY sPub)
|
|
(sPub', _) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "abcd" _ err4 <- signSendRecv r rKey ("abcd", rId, KEY sPub')
|
|
(err4, ERR AUTH) #== "rejects if secured with different key"
|
|
|
|
Resp "bcda" _ ok3 <- signSendRecv s sKey ("bcda", sId, _SEND "hello again")
|
|
(ok3, OK) #== "accepts signed SEND"
|
|
|
|
Resp "" _ (Msg mId2 msg2) <- tGet1 r
|
|
(dec mId2 msg2, Right "hello again") #== "delivers message 2"
|
|
|
|
Resp "cdab" _ ok5 <- signSendRecv r rKey ("cdab", rId, ACK mId2)
|
|
(ok5, OK) #== "replies OK when message acknowledged 2"
|
|
|
|
Resp "dabc" _ err5 <- sendRecv s ("", "dabc", sId, _SEND "hello")
|
|
(err5, ERR AUTH) #== "rejects unsigned SEND"
|
|
|
|
let maxAllowedMessage = B.replicate (maxMessageLength currentClientSMPRelayVersion) '-'
|
|
Resp "bcda" _ OK <- signSendRecv s sKey ("bcda", sId, _SEND maxAllowedMessage)
|
|
Resp "" _ (Msg mId3 msg3) <- tGet1 r
|
|
(dec mId3 msg3, Right maxAllowedMessage) #== "delivers message of max size"
|
|
|
|
let biggerMessage = B.replicate (maxMessageLength currentClientSMPRelayVersion + 1) '-'
|
|
Resp "bcda" _ (ERR LARGE_MSG) <- signSendRecv s sKey ("bcda", sId, _SEND biggerMessage)
|
|
pure ()
|
|
|
|
testCreateSndSecure :: SpecWith (ASrvTransport, AStoreType)
|
|
testCreateSndSecure =
|
|
it "should create (NEW) and secure (SKEY) queue by sender" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \r s -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv r rKey ("abcd", NoEntity, New rPub dhPub)
|
|
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
|
(rId1, NoEntity) #== "creates queue"
|
|
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "bcda" _ err2 <- sendRecv s (sampleSig, "bcda", sId, SKEY sPub)
|
|
(err2, ERR AUTH) #== "rejects SKEY with wrong signature"
|
|
|
|
Resp "cdab" _ err3 <- signSendRecv s sKey ("cdab", rId, SKEY sPub)
|
|
(err3, ERR AUTH) #== "rejects SKEY with recipients's ID"
|
|
|
|
Resp "dabc" sId2 OK <- signSendRecv s sKey ("dabc", sId, SKEY sPub)
|
|
(sId2, sId) #== "secures queue, same queue ID in response"
|
|
|
|
(sPub', sKey') <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "abcd" _ err4 <- signSendRecv s sKey' ("abcd", sId, SKEY sPub')
|
|
(err4, ERR AUTH) #== "rejects if secured with different key"
|
|
Resp "abcd" _ OK <- signSendRecv s sKey ("abcd", sId, SKEY sPub)
|
|
|
|
Resp "bcda" _ ok3 <- signSendRecv s sKey ("bcda", sId, _SEND "hello")
|
|
(ok3, OK) #== "accepts signed SEND"
|
|
|
|
Resp "" _ (Msg mId2 msg2) <- tGet1 r
|
|
(dec mId2 msg2, Right "hello") #== "delivers message"
|
|
|
|
Resp "cdab" _ ok5 <- signSendRecv r rKey ("cdab", rId, ACK mId2)
|
|
(ok5, OK) #== "replies OK when message acknowledged"
|
|
|
|
Resp "dabc" _ err5 <- sendRecv s ("", "dabc", sId, _SEND "hello")
|
|
(err5, ERR AUTH) #== "rejects unsigned SEND"
|
|
|
|
let maxAllowedMessage = B.replicate (maxMessageLength currentClientSMPRelayVersion) '-'
|
|
Resp "bcda" _ OK <- signSendRecv s sKey ("bcda", sId, _SEND maxAllowedMessage)
|
|
Resp "" _ (Msg mId3 msg3) <- tGet1 r
|
|
(dec mId3 msg3, Right maxAllowedMessage) #== "delivers message of max size"
|
|
|
|
let biggerMessage = B.replicate (maxMessageLength currentClientSMPRelayVersion + 1) '-'
|
|
Resp "bcda" _ (ERR LARGE_MSG) <- signSendRecv s sKey ("bcda", sId, _SEND biggerMessage)
|
|
pure ()
|
|
|
|
testSndSecureProhibited :: SpecWith (ASrvTransport, AStoreType)
|
|
testSndSecureProhibited =
|
|
it "should create (NEW) without allowing sndSecure and fail to and secure queue by sender (SKEY)" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \r s -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, _dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" rId1 (Ids _rId sId _srvDh) <- signSendRecv r rKey ("abcd", NoEntity, NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just (QRContact Nothing)) Nothing))
|
|
(rId1, NoEntity) #== "creates queue"
|
|
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "dabc" sId2 err <- signSendRecv s sKey ("dabc", sId, SKEY sPub)
|
|
(sId2, sId) #== "secures queue, same queue ID in response"
|
|
(err, ERR AUTH) #== "rejects SKEY when not allowed in NEW command"
|
|
|
|
testCreateUpdateKeys :: SpecWith (ASrvTransport, AStoreType)
|
|
testCreateUpdateKeys =
|
|
it "should create (NEW) and updated recipient keys (RKEY)" $ \(ATransport t, msType) ->
|
|
smpTest t msType $ \h -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(dhPub, _dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "1" NoEntity (Ids rId _sId _srvDh) <- signSendRecv h rKey ("1", NoEntity, NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just (QRContact Nothing)) Nothing))
|
|
(rPub', rKey') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
Resp "2" rId1 OK <- signSendRecv h rKey ("2", rId, RKEY [rPub, rPub'])
|
|
rId1 `shouldBe` rId
|
|
-- old key still works
|
|
Resp "3" rId2 (INFO _) <- signSendRecv h rKey ("3", rId, QUE)
|
|
rId2 `shouldBe` rId
|
|
-- new key works too
|
|
Resp "4" _ (INFO _) <- signSendRecv h rKey ("4", rId, QUE)
|
|
-- remove old key, only keep the new one
|
|
Resp "5" _ OK <- signSendRecv h rKey' ("5", rId, RKEY [rPub'])
|
|
-- old key no longer works
|
|
Resp "6" _ (ERR AUTH) <- signSendRecv h rKey ("6", rId, QUE)
|
|
-- add old key back
|
|
Resp "7" _ OK <- signSendRecv h rKey' ("7", rId, RKEY [rPub, rPub'])
|
|
-- old key works again
|
|
Resp "8" _ (INFO _) <- signSendRecv h rKey ("8", rId, QUE)
|
|
-- key can remove itself
|
|
Resp "9" _ OK <- signSendRecv h rKey ("9", rId, RKEY [rPub'])
|
|
-- old key does not work
|
|
Resp "10" _ (ERR AUTH) <- signSendRecv h rKey ("10", rId, QUE)
|
|
-- new key works
|
|
Resp "11" _ (INFO _) <- signSendRecv h rKey' ("11", rId, QUE)
|
|
pure ()
|
|
|
|
testCreateDelete :: SpecWith (ASrvTransport, AStoreType)
|
|
testCreateDelete =
|
|
it "should create (NEW), suspend (OFF) and delete (DEL) queue" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \rh sh -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv rh rKey ("abcd", NoEntity, New rPub dhPub)
|
|
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
|
(rId1, NoEntity) #== "creates queue"
|
|
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
Resp "bcda" _ ok1 <- signSendRecv rh rKey ("bcda", rId, KEY sPub)
|
|
(ok1, OK) #== "secures queue"
|
|
|
|
Resp "cdab" _ ok2 <- signSendRecv sh sKey ("cdab", sId, _SEND "hello")
|
|
(ok2, OK) #== "accepts signed SEND"
|
|
|
|
Resp "dabc" _ ok7 <- signSendRecv sh sKey ("dabc", sId, _SEND "hello 2")
|
|
(ok7, OK) #== "accepts signed SEND 2 - this message is not delivered because the first is not ACKed"
|
|
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 rh
|
|
(dec mId1 msg1, Right "hello") #== "delivers message"
|
|
|
|
Resp "abcd" _ err1 <- sendRecv rh (sampleSig, "abcd", rId, OFF)
|
|
(err1, ERR AUTH) #== "rejects OFF with wrong signature"
|
|
|
|
Resp "bcda" _ err2 <- signSendRecv rh rKey ("bcda", sId, OFF)
|
|
(err2, ERR AUTH) #== "rejects OFF with sender's ID"
|
|
|
|
Resp "cdab" rId2 ok3 <- signSendRecv rh rKey ("cdab", rId, OFF)
|
|
(ok3, OK) #== "suspends queue"
|
|
(rId2, rId) #== "same queue ID in response 2"
|
|
|
|
Resp "dabc" _ err3 <- signSendRecv sh sKey ("dabc", sId, _SEND "hello")
|
|
(err3, ERR AUTH) #== "rejects signed SEND"
|
|
|
|
Resp "abcd" _ err4 <- sendRecv sh ("", "abcd", sId, _SEND "hello")
|
|
(err4, ERR AUTH) #== "reject unsigned SEND too"
|
|
|
|
Resp "bcda" _ ok4 <- signSendRecv rh rKey ("bcda", rId, OFF)
|
|
(ok4, OK) #== "accepts OFF when suspended"
|
|
|
|
(Resp "cdab" _ (SOK Nothing), Resp "" _ (Msg mId2 msg2)) <- signSendRecv2 rh rKey ("cdab", rId, SUB)
|
|
(dec mId2 msg2, Right "hello") #== "accepts SUB when suspended and delivers the message again (because was not ACKed)"
|
|
|
|
Resp "dabc" _ err5 <- sendRecv rh (sampleSig, "dabc", rId, DEL)
|
|
(err5, ERR AUTH) #== "rejects DEL with wrong signature"
|
|
|
|
Resp "abcd" _ err6 <- signSendRecv rh rKey ("abcd", sId, DEL)
|
|
(err6, ERR AUTH) #== "rejects DEL with sender's ID"
|
|
|
|
Resp "bcda" rId3 ok6 <- signSendRecv rh rKey ("bcda", rId, DEL)
|
|
(ok6, OK) #== "deletes queue"
|
|
(rId3, rId) #== "same queue ID in response 3"
|
|
|
|
Resp "cdab" _ err7 <- signSendRecv sh sKey ("cdab", sId, _SEND "hello")
|
|
(err7, ERR AUTH) #== "rejects signed SEND when deleted"
|
|
|
|
Resp "dabc" _ err8 <- sendRecv sh ("", "dabc", sId, _SEND "hello")
|
|
(err8, ERR AUTH) #== "rejects unsigned SEND too when deleted"
|
|
|
|
Resp "abcd" _ err11 <- signSendRecv rh rKey ("abcd", rId, ACK "")
|
|
(err11, ERR AUTH) #== "rejects ACK when conn deleted - the second message is deleted"
|
|
|
|
Resp "bcda" _ err9 <- signSendRecv rh rKey ("bcda", rId, OFF)
|
|
(err9, ERR AUTH) #== "rejects OFF when deleted"
|
|
|
|
Resp "cdab" _ err10 <- signSendRecv rh rKey ("cdab", rId, SUB)
|
|
(err10, ERR AUTH) #== "rejects SUB when deleted"
|
|
|
|
stressTest :: SpecWith (ASrvTransport, AStoreType)
|
|
stressTest =
|
|
it "should create many queues, disconnect and re-connect" $ \(ATransport t, msType) ->
|
|
smpTest3 t msType $ \h1 h2 h3 -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(dhPub, _ :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
rIds <- forM ([1 .. 50] :: [Int]) . const $ do
|
|
Resp "" NoEntity (Ids rId _ _) <- signSendRecv h1 rKey ("", NoEntity, New rPub dhPub)
|
|
pure rId
|
|
let subscribeQueues h = forM_ rIds $ \rId -> do
|
|
Resp "" rId' (SOK Nothing) <- signSendRecv h rKey ("", rId, SUB)
|
|
rId' `shouldBe` rId
|
|
closeConnection $ connection h1
|
|
subscribeQueues h2
|
|
closeConnection $ connection h2
|
|
subscribeQueues h3
|
|
|
|
testAllowNewQueues :: SpecWith (ASrvTransport, AStoreType)
|
|
testAllowNewQueues =
|
|
it "should prohibit creating new queues with allowNewQueues = False" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do
|
|
let cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {allowNewQueues = False}
|
|
withSmpServerConfigOn (ATransport t) cfg' testPort $ \_ ->
|
|
testSMPClient @c $ \h -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, _ :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" NoEntity (ERR AUTH) <- signSendRecv h rKey ("abcd", NoEntity, New rPub dhPub)
|
|
pure ()
|
|
|
|
testDuplex :: SpecWith (ASrvTransport, AStoreType)
|
|
testDuplex =
|
|
it "should create 2 simplex connections and exchange messages" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \alice bob -> do
|
|
g <- C.newRandom
|
|
(arPub, arKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(aDhPub, aDhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" _ (Ids aRcv aSnd aSrvDh) <- signSendRecv alice arKey ("abcd", NoEntity, New arPub aDhPub)
|
|
let aDec = decryptMsgV3 $ C.dh' aSrvDh aDhPriv
|
|
-- aSnd ID is passed to Bob out-of-band
|
|
|
|
(bsPub, bsKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "bcda" _ OK <- sendRecv bob ("", "bcda", aSnd, _SEND $ "key " <> strEncode bsPub)
|
|
-- "key ..." is ad-hoc, not a part of SMP protocol
|
|
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 alice
|
|
Resp "cdab" _ OK <- signSendRecv alice arKey ("cdab", aRcv, ACK mId1)
|
|
Right ["key", bobKey] <- pure $ B.words <$> aDec mId1 msg1
|
|
(bobKey, strEncode bsPub) #== "key received from Bob"
|
|
Resp "dabc" _ OK <- signSendRecv alice arKey ("dabc", aRcv, KEY bsPub)
|
|
|
|
(brPub, brKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(bDhPub, bDhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" _ (Ids bRcv bSnd bSrvDh) <- signSendRecv bob brKey ("abcd", NoEntity, New brPub bDhPub)
|
|
let bDec = decryptMsgV3 $ C.dh' bSrvDh bDhPriv
|
|
Resp "bcda" _ OK <- signSendRecv bob bsKey ("bcda", aSnd, _SEND $ "reply_id " <> B64.encode (unEntityId bSnd))
|
|
-- "reply_id ..." is ad-hoc, not a part of SMP protocol
|
|
|
|
Resp "" _ (Msg mId2 msg2) <- tGet1 alice
|
|
Resp "cdab" _ OK <- signSendRecv alice arKey ("cdab", aRcv, ACK mId2)
|
|
Right ["reply_id", bId] <- pure $ B.words <$> aDec mId2 msg2
|
|
(bId, B64.encode (unEntityId bSnd)) #== "reply queue ID received from Bob"
|
|
|
|
(asPub, asKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "dabc" _ OK <- sendRecv alice ("", "dabc", bSnd, _SEND $ "key " <> strEncode asPub)
|
|
-- "key ..." is ad-hoc, not a part of SMP protocol
|
|
|
|
Resp "" _ (Msg mId3 msg3) <- tGet1 bob
|
|
Resp "abcd" _ OK <- signSendRecv bob brKey ("abcd", bRcv, ACK mId3)
|
|
Right ["key", aliceKey] <- pure $ B.words <$> bDec mId3 msg3
|
|
(aliceKey, strEncode asPub) #== "key received from Alice"
|
|
Resp "bcda" _ OK <- signSendRecv bob brKey ("bcda", bRcv, KEY asPub)
|
|
|
|
Resp "cdab" _ OK <- signSendRecv bob bsKey ("cdab", aSnd, _SEND "hi alice")
|
|
|
|
Resp "" _ (Msg mId4 msg4) <- tGet1 alice
|
|
Resp "dabc" _ OK <- signSendRecv alice arKey ("dabc", aRcv, ACK mId4)
|
|
(aDec mId4 msg4, Right "hi alice") #== "message received from Bob"
|
|
|
|
Resp "abcd" _ OK <- signSendRecv alice asKey ("abcd", bSnd, _SEND "how are you bob")
|
|
|
|
Resp "" _ (Msg mId5 msg5) <- tGet1 bob
|
|
Resp "bcda" _ OK <- signSendRecv bob brKey ("bcda", bRcv, ACK mId5)
|
|
(bDec mId5 msg5, Right "how are you bob") #== "message received from alice"
|
|
|
|
testSwitchSub :: SpecWith (ASrvTransport, AStoreType)
|
|
testSwitchSub =
|
|
it "should create simplex connections and switch subscription to another TCP connection" $ \(ATransport t, msType) ->
|
|
smpTest3 t msType $ \rh1 rh2 sh -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" _ (Ids rId sId srvDh) <- signSendRecv rh1 rKey ("abcd", NoEntity, New rPub dhPub)
|
|
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
|
Resp "bcda" _ ok1 <- sendRecv sh ("", "bcda", sId, _SEND "test1")
|
|
(ok1, OK) #== "sent test message 1"
|
|
Resp "cdab" _ ok2 <- sendRecv sh ("", "cdab", sId, _SEND "test2, no ACK")
|
|
(ok2, OK) #== "sent test message 2"
|
|
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 rh1
|
|
(dec mId1 msg1, Right "test1") #== "test message 1 delivered to the 1st TCP connection"
|
|
Resp "abcd" _ (Msg mId2 msg2) <- signSendRecv rh1 rKey ("abcd", rId, ACK mId1)
|
|
(dec mId2 msg2, Right "test2, no ACK") #== "test message 2 delivered, no ACK"
|
|
|
|
(Resp "bcda" _ (SOK Nothing), Resp "" _ (Msg mId2' msg2')) <- signSendRecv2 rh2 rKey ("bcda", rId, SUB)
|
|
(dec mId2' msg2', Right "test2, no ACK") #== "same simplex queue via another TCP connection, tes2 delivered again (no ACK in 1st queue)"
|
|
Resp "cdab" _ OK <- signSendRecv rh2 rKey ("cdab", rId, ACK mId2')
|
|
|
|
Resp "" _ end <- tGet1 rh1
|
|
(end, END) #== "unsubscribed the 1st TCP connection"
|
|
|
|
Resp "dabc" _ OK <- sendRecv sh ("", "dabc", sId, _SEND "test3")
|
|
|
|
Resp "" _ (Msg mId3 msg3) <- tGet1 rh2
|
|
(dec mId3 msg3, Right "test3") #== "delivered to the 2nd TCP connection"
|
|
|
|
Resp "abcd" _ err <- signSendRecv rh1 rKey ("abcd", rId, ACK mId3)
|
|
(err, ERR NO_MSG) #== "rejects ACK from the 1st TCP connection"
|
|
|
|
Resp "bcda" _ ok3 <- signSendRecv rh2 rKey ("bcda", rId, ACK mId3)
|
|
(ok3, OK) #== "accepts ACK from the 2nd TCP connection"
|
|
|
|
Resp "cdab" _ OK <- signSendRecv rh1 rKey ("cdab", rId, DEL)
|
|
Resp "" rId' DELD <- tGet1 rh2
|
|
(rId', rId) #== "connection deleted event delivered to subscribed client"
|
|
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg rh1 >>= \case
|
|
Nothing -> return ()
|
|
Just _ -> error "nothing else is delivered to the 1st TCP connection"
|
|
|
|
testGetCommand :: SpecWith (ASrvTransport, AStoreType)
|
|
testGetCommand =
|
|
it "should retrieve messages from the queue using GET command" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
smpTest t msType $ \sh -> do
|
|
queue <- newEmptyTMVarIO
|
|
testSMPClient @c $ \rh ->
|
|
atomically . putTMVar queue =<< createAndSecureQueue rh sPub
|
|
testSMPClient @c $ \rh -> do
|
|
(sId, rId, rKey, dhShared) <- atomically $ takeTMVar queue
|
|
let dec = decryptMsgV3 dhShared
|
|
Resp "1" _ OK <- signSendRecv sh sKey ("1", sId, _SEND "hello")
|
|
Resp "2" _ (Msg mId1 msg1) <- signSendRecv rh rKey ("2", rId, GET)
|
|
(dec mId1 msg1, Right "hello") #== "retrieved from queue"
|
|
Resp "3" _ OK <- signSendRecv rh rKey ("3", rId, ACK mId1)
|
|
Resp "4" _ OK <- signSendRecv rh rKey ("4", rId, GET)
|
|
pure ()
|
|
|
|
testGetSubCommands :: SpecWith (ASrvTransport, AStoreType)
|
|
testGetSubCommands =
|
|
it "should retrieve messages with GET and receive with SUB, only one ACK would work" $ \(ATransport t, msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
smpTest3 t msType $ \rh1 rh2 sh -> do
|
|
(sId, rId, rKey, dhShared) <- createAndSecureQueue rh1 sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
Resp "1" _ OK <- signSendRecv sh sKey ("1", sId, _SEND "hello 1")
|
|
Resp "1a" _ OK <- signSendRecv sh sKey ("1a", sId, _SEND "hello 2")
|
|
Resp "1b" _ OK <- signSendRecv sh sKey ("1b", sId, _SEND "hello 3")
|
|
Resp "1c" _ OK <- signSendRecv sh sKey ("1c", sId, _SEND "hello 4")
|
|
-- both get the same if not ACK'd
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 rh1
|
|
Resp "2" _ (Msg mId1' msg1') <- signSendRecv rh2 rKey ("2", rId, GET)
|
|
(dec mId1 msg1, Right "hello 1") #== "received from queue via SUB"
|
|
(dec mId1' msg1', Right "hello 1") #== "retrieved from queue with GET"
|
|
mId1 `shouldBe` mId1'
|
|
msg1 `shouldBe` msg1'
|
|
-- subscriber cannot GET, getter cannot SUB
|
|
Resp "3" _ (ERR (CMD PROHIBITED)) <- signSendRecv rh1 rKey ("3", rId, GET)
|
|
Resp "3a" _ (ERR (CMD PROHIBITED)) <- signSendRecv rh2 rKey ("3a", rId, SUB)
|
|
-- ACK for SUB delivers the next message
|
|
Resp "4" _ (Msg mId2 msg2) <- signSendRecv rh1 rKey ("4", rId, ACK mId1)
|
|
(dec mId2 msg2, Right "hello 2") #== "received from queue via SUB"
|
|
-- bad msgId returns error
|
|
Resp "5" _ (ERR NO_MSG) <- signSendRecv rh2 rKey ("5", rId, ACK "1234")
|
|
-- already ACK'd by subscriber, but still returns OK when msgId matches
|
|
Resp "5a" _ OK <- signSendRecv rh2 rKey ("5a", rId, ACK mId1)
|
|
-- msg2 is not lost - even if subscriber does not ACK it, it is delivered to getter
|
|
Resp "6" _ (Msg mId2' msg2') <- signSendRecv rh2 rKey ("6", rId, GET)
|
|
(dec mId2' msg2', Right "hello 2") #== "retrieved from queue with GET"
|
|
mId2 `shouldBe` mId2'
|
|
msg2 `shouldBe` msg2'
|
|
-- getter ACK returns OK, even though there is the next message
|
|
Resp "7" _ OK <- signSendRecv rh2 rKey ("7", rId, ACK mId2')
|
|
Resp "8" _ (Msg mId3 msg3) <- signSendRecv rh2 rKey ("8", rId, GET)
|
|
(dec mId3 msg3, Right "hello 3") #== "retrieved from queue with GET"
|
|
-- subscriber ACK does not lose message
|
|
Resp "9" _ (Msg mId3' msg3') <- signSendRecv rh1 rKey ("9", rId, ACK mId2')
|
|
(dec mId3' msg3', Right "hello 3") #== "retrieved from queue with GET"
|
|
mId3 `shouldBe` mId3'
|
|
msg3 `shouldBe` msg3'
|
|
Resp "10" _ (Msg mId4 msg4) <- signSendRecv rh1 rKey ("10", rId, ACK mId3)
|
|
(dec mId4 msg4, Right "hello 4") #== "retrieved from queue with GET"
|
|
Resp "11" _ OK <- signSendRecv rh1 rKey ("11", rId, ACK mId4)
|
|
-- no more messages for getter too
|
|
Resp "12" _ OK <- signSendRecv rh2 rKey ("12", rId, GET)
|
|
pure ()
|
|
|
|
testExceedQueueQuota :: SpecWith (ASrvTransport, AStoreType)
|
|
testExceedQueueQuota =
|
|
it "should reply with ERR QUOTA to sender and send QUOTA message to the recipient" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do
|
|
let cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {msgQueueQuota = 2}
|
|
withSmpServerConfigOn (ATransport t) cfg' testPort $ \_ ->
|
|
testSMPClient @c $ \sh -> testSMPClient @c $ \rh -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(sId, rId, rKey, dhShared) <- createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
Resp "1" _ OK <- signSendRecv sh sKey ("1", sId, _SEND "hello 1")
|
|
Resp "2" _ OK <- signSendRecv sh sKey ("2", sId, _SEND "hello 2")
|
|
Resp "3" _ (ERR QUOTA) <- signSendRecv sh sKey ("3", sId, _SEND "hello 3")
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 rh
|
|
(dec mId1 msg1, Right "hello 1") #== "hello 1"
|
|
Resp "4" _ (Msg mId2 msg2) <- signSendRecv rh rKey ("4", rId, ACK mId1)
|
|
(dec mId2 msg2, Right "hello 2") #== "hello 2"
|
|
Resp "5" _ (ERR QUOTA) <- signSendRecv sh sKey ("5", sId, _SEND "hello 3")
|
|
Resp "6" _ (Msg mId3 msg3) <- signSendRecv rh rKey ("6", rId, ACK mId2)
|
|
(dec mId3 msg3, Left "ClientRcvMsgQuota") #== "ClientRcvMsgQuota"
|
|
Resp "7" _ (ERR QUOTA) <- signSendRecv sh sKey ("7", sId, _SEND "hello 3")
|
|
Resp "8" _ OK <- signSendRecv rh rKey ("8", rId, ACK mId3)
|
|
Resp "9" _ OK <- signSendRecv sh sKey ("9", sId, _SEND "hello 3")
|
|
Resp "" _ (Msg mId4 msg4) <- tGet1 rh
|
|
(dec mId4 msg4, Right "hello 3") #== "hello 3"
|
|
Resp "10" _ OK <- signSendRecv rh rKey ("10", rId, ACK mId4)
|
|
pure ()
|
|
|
|
testConcurrentSendDelivery :: SpecWith (ASrvTransport, AStoreType)
|
|
testConcurrentSendDelivery =
|
|
it "should continue delivering messages if message is sent before it is acknowledged" $ \(ATransport t, msType) -> do
|
|
g <- C.newRandom
|
|
smpTest3 t msType $ \rh sh1 sh2 -> do
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(sId, rId, rKey, dhShared) <- createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
sndMsg sh n = do
|
|
Resp (CorrId n') _ OK <- signSendRecv sh sKey (n, sId, _SEND ("msg " <> n))
|
|
n' `shouldBe` n
|
|
isMsg1or2 mId msg = dec mId msg == Right "msg 1" || dec mId msg == Right "msg 2" `shouldBe` True
|
|
replicateM_ 50 $ do
|
|
concurrently_ (sndMsg sh1 "1") (sndMsg sh2 "2")
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 rh
|
|
isMsg1or2 mId1 msg1
|
|
Resp "3" _ (Msg mId2 msg2) <- signSendRecv rh rKey ("3", rId, ACK mId1)
|
|
isMsg1or2 mId2 msg2
|
|
Resp "4" _ OK <- signSendRecv rh rKey ("4", rId, ACK mId2)
|
|
pure ()
|
|
|
|
testWithStoreLog :: SpecWith (ASrvTransport, AStoreType)
|
|
testWithStoreLog =
|
|
it "should store simplex queues to log and restore them after server restart" $ \(at@(ATransport t), msType) -> do
|
|
g <- C.newRandom
|
|
(sPub1, sKey1) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(sPub2, sKey2) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(nPub, nKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
recipientId1 <- newTVarIO NoEntity
|
|
recipientKey1 <- newTVarIO Nothing
|
|
dhShared1 <- newTVarIO Nothing
|
|
senderId1 <- newTVarIO NoEntity
|
|
senderId2 <- newTVarIO NoEntity
|
|
notifierId <- newTVarIO NoEntity
|
|
|
|
let (cfg', compacting) = serverStoreLogCfg msType
|
|
withSmpServerConfigOn at cfg' testPort . runTest t $ \h -> runClient t $ \h1 -> do
|
|
(sId1, rId1, rKey1, dhShared) <- createAndSecureQueue h sPub1
|
|
(rcvNtfPubDhKey, _) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" _ (NID nId _) <- signSendRecv h rKey1 ("abcd", rId1, NKEY nPub rcvNtfPubDhKey)
|
|
atomically $ do
|
|
writeTVar recipientId1 rId1
|
|
writeTVar recipientKey1 $ Just rKey1
|
|
writeTVar dhShared1 $ Just dhShared
|
|
writeTVar senderId1 sId1
|
|
writeTVar notifierId nId
|
|
Resp "dabc" _ (SOK Nothing) <- signSendRecv h1 nKey ("dabc", nId, NSUB)
|
|
(mId1, msg1) <-
|
|
signSendRecv h sKey1 ("bcda", sId1, _SEND' "hello") >>= \case
|
|
Resp "" _ (Msg mId1 msg1) -> pure (mId1, msg1)
|
|
r -> error $ "unexpected response " <> take 100 (show r)
|
|
Resp "bcda" _ OK <- tGet1 h
|
|
(decryptMsgV3 dhShared mId1 msg1, Right "hello") #== "delivered from queue 1"
|
|
Resp "" _ (NMSG _ _) <- tGet1 h1
|
|
|
|
(sId2, rId2, rKey2, dhShared2) <- createAndSecureQueue h sPub2
|
|
atomically $ writeTVar senderId2 sId2
|
|
(mId2, msg2) <-
|
|
signSendRecv h sKey2 ("cdab", sId2, _SEND "hello too") >>= \case
|
|
Resp "" _ (Msg mId2 msg2) -> pure (mId2, msg2)
|
|
r -> error $ "unexpected response " <> take 100 (show r)
|
|
Resp "cdab" _ OK <- tGet1 h
|
|
(decryptMsgV3 dhShared2 mId2 msg2, Right "hello too") #== "delivered from queue 2"
|
|
|
|
Resp "dabc" _ OK <- signSendRecv h rKey2 ("dabc", rId2, DEL)
|
|
pure ()
|
|
|
|
logSize testStoreLogFile `shouldReturn` 6
|
|
|
|
let cfg'' = withServerCfg cfg $ \cfg_ -> ASrvCfg SQSMemory SMSMemory cfg_ {serverStoreCfg = SSCMemory Nothing}
|
|
withSmpServerConfigOn at cfg'' testPort . runTest t $ \h -> do
|
|
sId1 <- readTVarIO senderId1
|
|
-- fails if store log is disabled
|
|
Resp "bcda" _ (ERR AUTH) <- signSendRecv h sKey1 ("bcda", sId1, _SEND "hello")
|
|
pure ()
|
|
|
|
withSmpServerConfigOn at cfg' testPort . runTest t $ \h -> runClient t $ \h1 -> do
|
|
-- this queue is restored
|
|
rId1 <- readTVarIO recipientId1
|
|
Just rKey1 <- readTVarIO recipientKey1
|
|
Just dh1 <- readTVarIO dhShared1
|
|
sId1 <- readTVarIO senderId1
|
|
nId <- readTVarIO notifierId
|
|
Resp "dabc" _ (SOK Nothing) <- signSendRecv h1 nKey ("dabc", nId, NSUB)
|
|
Resp "bcda" _ OK <- signSendRecv h sKey1 ("bcda", sId1, _SEND' "hello")
|
|
(Resp "cdab" _ (SOK Nothing), Resp "" _ (Msg mId3 msg3)) <- signSendRecv2 h rKey1 ("cdab", rId1, SUB)
|
|
(decryptMsgV3 dh1 mId3 msg3, Right "hello") #== "delivered from restored queue"
|
|
Resp "" _ (NMSG _ _) <- tGet1 h1
|
|
-- this queue is removed - not restored
|
|
sId2 <- readTVarIO senderId2
|
|
Resp "cdab" _ (ERR AUTH) <- signSendRecv h sKey2 ("cdab", sId2, _SEND "hello too")
|
|
pure ()
|
|
|
|
-- when (usesStoreLog ps) $ do
|
|
logSize testStoreLogFile `shouldReturn` (if compacting then 1 else 6)
|
|
removeFile testStoreLogFile
|
|
where
|
|
runTest :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
|
|
runTest _ test' server = do
|
|
testSMPClient test' `shouldReturn` ()
|
|
killThread server
|
|
|
|
runClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> Expectation
|
|
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
|
|
|
serverStoreLogCfg :: AStoreType -> (AServerConfig, Bool)
|
|
serverStoreLogCfg msType =
|
|
let cfg' =
|
|
withServerCfg (cfgMS msType) $ \cfg_ ->
|
|
withStoreCfg (serverStoreConfig_ True msType) $ \serverStoreCfg ->
|
|
cfg_ {serverStoreCfg, storeNtfsFile = Just testStoreNtfsFile, serverStatsBackupFile = Just testServerStatsBackupFile}
|
|
compacting = case msType of
|
|
ASType SQSPostgres _ -> False
|
|
_ -> True
|
|
in (cfg', compacting)
|
|
|
|
logSize :: FilePath -> IO Int
|
|
logSize f = go (10 :: Int)
|
|
where
|
|
go n =
|
|
try (length . B.lines <$> B.readFile f) >>= \case
|
|
Right l -> pure l
|
|
Left (e :: SomeException)
|
|
| n > 0 -> threadDelay 100000 >> go (n - 1)
|
|
| otherwise -> throwIO e
|
|
|
|
testRestoreMessages :: SpecWith (ASrvTransport, AStoreType)
|
|
testRestoreMessages =
|
|
it "should store messages on exit and restore on start" $ \(at@(ATransport t), msType) -> do
|
|
removeFileIfExists testStoreLogFile
|
|
removeFileIfExists testStoreMsgsFile
|
|
whenM (doesDirectoryExist testStoreMsgsDir) $ removeDirectoryRecursive testStoreMsgsDir
|
|
removeFileIfExists testServerStatsBackupFile
|
|
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
recipientId <- newTVarIO NoEntity
|
|
recipientKey <- newTVarIO Nothing
|
|
dhShared <- newTVarIO Nothing
|
|
senderId <- newTVarIO NoEntity
|
|
let (cfg', compacting) = serverStoreLogCfg msType
|
|
withSmpServerConfigOn at cfg' testPort . runTest t $ \h -> do
|
|
runClient t $ \h1 -> do
|
|
(sId, rId, rKey, dh) <- createAndSecureQueue h1 sPub
|
|
atomically $ do
|
|
writeTVar recipientId rId
|
|
writeTVar recipientKey $ Just rKey
|
|
writeTVar dhShared $ Just dh
|
|
writeTVar senderId sId
|
|
Resp "1" _ OK <- signSendRecv h sKey ("1", sId, _SEND "hello")
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 h1
|
|
Resp "1a" _ OK <- signSendRecv h1 rKey ("1a", rId, ACK mId1)
|
|
(decryptMsgV3 dh mId1 msg1, Right "hello") #== "message delivered"
|
|
-- messages below are delivered after server restart
|
|
sId <- readTVarIO senderId
|
|
Resp "2" _ OK <- signSendRecv h sKey ("2", sId, _SEND "hello 2")
|
|
Resp "3" _ OK <- signSendRecv h sKey ("3", sId, _SEND "hello 3")
|
|
Resp "4" _ OK <- signSendRecv h sKey ("4", sId, _SEND "hello 4")
|
|
Resp "5" _ OK <- signSendRecv h sKey ("5", sId, _SEND "hello 5")
|
|
Resp "6" _ (ERR QUOTA) <- signSendRecv h sKey ("6", sId, _SEND "hello 6")
|
|
pure ()
|
|
rId <- readTVarIO recipientId
|
|
logSize testStoreLogFile `shouldReturn` 2
|
|
logSize testServerStatsBackupFile `shouldReturn` 95
|
|
Right stats1 <- strDecode <$> B.readFile testServerStatsBackupFile
|
|
checkStats stats1 [rId] 5 1
|
|
withSmpServerConfigOn at cfg' testPort . runTest t $ \h -> do
|
|
Just rKey <- readTVarIO recipientKey
|
|
Just dh <- readTVarIO dhShared
|
|
let dec = decryptMsgV3 dh
|
|
(Resp "2" _ (SOK Nothing), Resp "" _ (Msg mId2 msg2)) <- signSendRecv2 h rKey ("2", rId, SUB)
|
|
(dec mId2 msg2, Right "hello 2") #== "restored message delivered"
|
|
Resp "3" _ (Msg mId3 msg3) <- signSendRecv h rKey ("3", rId, ACK mId2)
|
|
(dec mId3 msg3, Right "hello 3") #== "restored message delivered"
|
|
Resp "4" _ (Msg mId4 msg4) <- signSendRecv h rKey ("4", rId, ACK mId3)
|
|
(dec mId4 msg4, Right "hello 4") #== "restored message delivered"
|
|
logSize testStoreLogFile `shouldReturn` (if compacting then 1 else 2)
|
|
-- the last message is not removed because it was not ACK'd
|
|
-- logSize testStoreMsgsFile `shouldReturn` 3
|
|
logSize testServerStatsBackupFile `shouldReturn` 95
|
|
Right stats2 <- strDecode <$> B.readFile testServerStatsBackupFile
|
|
checkStats stats2 [rId] 5 3
|
|
|
|
withSmpServerConfigOn at cfg' testPort . runTest t $ \h -> do
|
|
Just rKey <- readTVarIO recipientKey
|
|
Just dh <- readTVarIO dhShared
|
|
let dec = decryptMsgV3 dh
|
|
(Resp "4" _ (SOK Nothing), Resp "" _ (Msg mId4 msg4)) <- signSendRecv2 h rKey ("4", rId, SUB)
|
|
(dec mId4 msg4, Right "hello 4") #== "restored message delivered"
|
|
Resp "5" _ (Msg mId5 msg5) <- signSendRecv h rKey ("5", rId, ACK mId4)
|
|
(dec mId5 msg5, Right "hello 5") #== "restored message delivered"
|
|
Resp "6" _ (Msg mId6 msg6) <- signSendRecv h rKey ("6", rId, ACK mId5)
|
|
(dec mId6 msg6, Left "ClientRcvMsgQuota") #== "restored message delivered"
|
|
Resp "7" _ OK <- signSendRecv h rKey ("7", rId, ACK mId6)
|
|
pure ()
|
|
logSize testStoreLogFile `shouldReturn` (if compacting then 1 else 2)
|
|
removeFile testStoreLogFile
|
|
logSize testServerStatsBackupFile `shouldReturn` 95
|
|
Right stats3 <- strDecode <$> B.readFile testServerStatsBackupFile
|
|
checkStats stats3 [rId] 5 5
|
|
removeFileIfExists testStoreMsgsFile
|
|
whenM (doesDirectoryExist testStoreMsgsDir) $ removeDirectoryRecursive testStoreMsgsDir
|
|
removeFile testServerStatsBackupFile
|
|
where
|
|
runTest :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
|
|
runTest _ test' server = do
|
|
testSMPClient test' `shouldReturn` ()
|
|
killThread server
|
|
|
|
runClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> Expectation
|
|
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
|
|
|
checkStats :: ServerStatsData -> [RecipientId] -> Int -> Int -> Expectation
|
|
checkStats s qs sent received = do
|
|
_qCreated s `shouldBe` length qs
|
|
_qSecured s `shouldBe` length qs
|
|
_qDeletedAll s `shouldBe` 0
|
|
_qDeletedNew s `shouldBe` 0
|
|
_qDeletedSecured s `shouldBe` 0
|
|
_msgSent s `shouldBe` sent
|
|
_msgRecv s `shouldBe` received
|
|
_msgSentNtf s `shouldBe` 0
|
|
_msgRecvNtf s `shouldBe` 0
|
|
let PeriodStatsData {_day, _week, _month} = _activeQueues s
|
|
IS.toList _day `shouldBe` map (hash . unEntityId) qs
|
|
IS.toList _week `shouldBe` map (hash . unEntityId) qs
|
|
IS.toList _month `shouldBe` map (hash . unEntityId) qs
|
|
|
|
testRestoreExpireMessages :: SpecWith (ASrvTransport, AStoreType)
|
|
testRestoreExpireMessages =
|
|
it "should store messages on exit and restore on start (old / v2)" $ \(at@(ATransport t), msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
recipientId <- newTVarIO NoEntity
|
|
recipientKey <- newTVarIO Nothing
|
|
dhShared <- newTVarIO Nothing
|
|
senderId <- newTVarIO NoEntity
|
|
let (cfg', _compacting) = serverStoreLogCfg msType
|
|
withSmpServerConfigOn at cfg' testPort . runTest t $ \h -> do
|
|
runClient t $ \h1 -> do
|
|
(sId, rId, rKey, dh) <- createAndSecureQueue h1 sPub
|
|
atomically $ do
|
|
writeTVar recipientId rId
|
|
writeTVar recipientKey $ Just rKey
|
|
writeTVar dhShared $ Just dh
|
|
writeTVar senderId sId
|
|
sId <- readTVarIO senderId
|
|
Resp "1" _ OK <- signSendRecv h sKey ("1", sId, _SEND "hello 1")
|
|
Resp "2" _ OK <- signSendRecv h sKey ("2", sId, _SEND "hello 2")
|
|
threadDelay 3000000
|
|
Resp "3" _ OK <- signSendRecv h sKey ("3", sId, _SEND "hello 3")
|
|
Resp "4" _ OK <- signSendRecv h sKey ("4", sId, _SEND "hello 4")
|
|
pure ()
|
|
logSize testStoreLogFile `shouldReturn` 2
|
|
exportStoreMessages msType
|
|
msgs <- B.readFile testStoreMsgsFile
|
|
length (B.lines msgs) `shouldBe` 4
|
|
|
|
let expCfg1 = Just ExpirationConfig {ttl = 86400, checkInterval = 43200}
|
|
cfg1 = updateCfg cfg' $ \cfg_ -> cfg_ {messageExpiration = expCfg1, serverStatsBackupFile = Just testServerStatsBackupFile}
|
|
withSmpServerConfigOn at cfg1 testPort . runTest t $ \_ -> pure ()
|
|
|
|
logSize testStoreLogFile `shouldReturn` 1
|
|
exportStoreMessages msType
|
|
msgs' <- B.readFile testStoreMsgsFile
|
|
msgs' `shouldBe` msgs
|
|
let expCfg2 = Just ExpirationConfig {ttl = 2, checkInterval = 43200}
|
|
cfg2 = updateCfg cfg' $ \cfg_ -> cfg_ {messageExpiration = expCfg2, serverStatsBackupFile = Just testServerStatsBackupFile}
|
|
withSmpServerConfigOn at cfg2 testPort . runTest t $ \_ -> pure ()
|
|
|
|
logSize testStoreLogFile `shouldReturn` 1
|
|
-- two messages expired
|
|
exportStoreMessages msType
|
|
msgs'' <- B.readFile testStoreMsgsFile
|
|
length (B.lines msgs'') `shouldBe` 2
|
|
B.lines msgs'' `shouldBe` drop 2 (B.lines msgs)
|
|
Right ServerStatsData {_msgExpired} <- strDecode <$> B.readFile testServerStatsBackupFile
|
|
_msgExpired `shouldBe` 2
|
|
where
|
|
exportStoreMessages :: AStoreType -> IO ()
|
|
exportStoreMessages = \case
|
|
ASType _ SMSJournal -> export
|
|
ASType _ SMSPostgres -> exportDB
|
|
ASType _ SMSMemory -> pure ()
|
|
where
|
|
export = do
|
|
ms <- readWriteQueues
|
|
exportMessages False (StoreJournal ms) testStoreMsgsFile False
|
|
closeMsgStore ms
|
|
#if defined(dbServerPostgres)
|
|
exportDB = do
|
|
readWriteQueues >>= closeMsgStore
|
|
ms' <- newMsgStore (testPostgresStoreConfig {quota = 4} :: PostgresMsgStoreCfg)
|
|
_n <- withFile testStoreMsgsFile WriteMode $ exportDbMessages False ms'
|
|
closeMsgStore ms'
|
|
#else
|
|
exportDB = error "compiled without server_postgres flag"
|
|
#endif
|
|
readWriteQueues = do
|
|
ms <- newMsgStore ((testJournalStoreCfg MQStoreCfg) {quota = 4} :: JournalStoreConfig 'QSMemory)
|
|
readWriteQueueStore True (mkQueue ms True) testStoreLogFile (stmQueueStore ms) >>= closeStoreLog
|
|
removeFileIfExists testStoreMsgsFile
|
|
pure ms
|
|
runTest :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
|
|
runTest _ test' server = do
|
|
testSMPClient test' `shouldReturn` ()
|
|
killThread server
|
|
|
|
runClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> Expectation
|
|
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
|
|
|
testPrometheusMetrics :: SpecWith (ASrvTransport, AStoreType)
|
|
testPrometheusMetrics =
|
|
it "should save Prometheus metrics" $ \(at, msType) -> do
|
|
let cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {prometheusInterval = Just 1}
|
|
withSmpServerConfigOn at cfg' testPort $ \_ -> threadDelay 1000000
|
|
doesFileExist testPrometheusMetricsFile `shouldReturn` True
|
|
|
|
createAndSecureQueue :: Transport c => THandleSMP c 'TClient -> SndPublicAuthKey -> IO (SenderId, RecipientId, RcvPrivateAuthKey, RcvDhSecret)
|
|
createAndSecureQueue h sPub = do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" NoEntity (Ids rId sId srvDh) <- signSendRecv h rKey ("abcd", NoEntity, New rPub dhPub)
|
|
let dhShared = C.dh' srvDh dhPriv
|
|
Resp "dabc" rId' OK <- signSendRecv h rKey ("dabc", rId, KEY sPub)
|
|
(rId', rId) #== "same queue ID"
|
|
pure (sId, rId, rKey, dhShared)
|
|
|
|
testTiming :: SpecWith (ASrvTransport, AStoreType)
|
|
testTiming =
|
|
describe "should have similar time for auth error, whether queue exists or not, for all key types" $
|
|
forM_ timingTests $ \tst ->
|
|
it (testName tst) $ \(ATransport t, msType) ->
|
|
smpTest2Cfg (cfgMS msType) (mkVersionRange minServerSMPRelayVersion authCmdsSMPVersion) t $ \rh sh ->
|
|
testSameTiming rh sh tst msType
|
|
where
|
|
testName :: (C.AuthAlg, C.AuthAlg, Int) -> String
|
|
testName (C.AuthAlg goodKeyAlg, C.AuthAlg badKeyAlg, _) = unwords ["queue key:", show goodKeyAlg, "/ used key:", show badKeyAlg]
|
|
timingTests :: [(C.AuthAlg, C.AuthAlg, Int)]
|
|
timingTests =
|
|
[ (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd25519, 200), -- correct key type
|
|
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd448, 150),
|
|
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SX25519, 200),
|
|
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd25519, 200),
|
|
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd448, 150), -- correct key type
|
|
(C.AuthAlg C.SEd448, C.AuthAlg C.SX25519, 200),
|
|
(C.AuthAlg C.SX25519, C.AuthAlg C.SEd25519, 200),
|
|
(C.AuthAlg C.SX25519, C.AuthAlg C.SEd448, 150),
|
|
(C.AuthAlg C.SX25519, C.AuthAlg C.SX25519, 200) -- correct key type
|
|
]
|
|
timeRepeat n = fmap fst . timeItT . forM_ (replicate n ()) . const
|
|
similarTime t1 t2 msType
|
|
| t1 <= t2 = abs (1 - t1 / t2) < diff
|
|
| otherwise = similarTime t2 t1 msType
|
|
where
|
|
-- normally the difference between "no queue" and "wrong key" is less than 5%, but it's higher on PostgreSQL and on CI
|
|
diff = case msType of
|
|
ASType SQSPostgres _ -> 0.45
|
|
_ -> 0.3
|
|
testSameTiming :: forall c. Transport c => THandleSMP c 'TClient -> THandleSMP c 'TClient -> (C.AuthAlg, C.AuthAlg, Int) -> AStoreType -> Expectation
|
|
testSameTiming rh sh (C.AuthAlg goodKeyAlg, C.AuthAlg badKeyAlg, n) msType = do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair goodKeyAlg g
|
|
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" NoEntity (Ids rId sId srvDh) <- signSendRecv rh rKey ("abcd", NoEntity, New rPub dhPub)
|
|
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
|
Resp "cdab" _ OK <- signSendRecv rh rKey ("cdab", rId, SUB)
|
|
|
|
(_, badKey) <- atomically $ C.generateAuthKeyPair badKeyAlg g
|
|
runTimingTest rh badKey rId SUB
|
|
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair goodKeyAlg g
|
|
Resp "dabc" _ OK <- signSendRecv rh rKey ("dabc", rId, KEY sPub)
|
|
|
|
Resp "bcda" _ OK <- signSendRecv sh sKey ("bcda", sId, _SEND "hello")
|
|
Resp "" _ (Msg mId msg) <- tGet1 rh
|
|
(dec mId msg, Right "hello") #== "delivered from queue"
|
|
|
|
runTimingTest sh badKey sId $ _SEND "hello"
|
|
where
|
|
runTimingTest :: PartyI p => THandleSMP c 'TClient -> C.APrivateAuthKey -> EntityId -> Command p -> IO ()
|
|
runTimingTest h badKey qId cmd = do
|
|
threadDelay 100000
|
|
_ <- timeRepeat n $ do
|
|
-- "warm up" the server
|
|
Resp "dabc" _ (ERR AUTH) <- signSendRecv h badKey ("dabc", EntityId "1234", cmd)
|
|
return ()
|
|
threadDelay 100000
|
|
timeWrongKey <- timeRepeat n $ do
|
|
Resp "cdab" _ (ERR AUTH) <- signSendRecv h badKey ("cdab", qId, cmd)
|
|
return ()
|
|
threadDelay 100000
|
|
timeNoQueue <- timeRepeat n $ do
|
|
Resp "dabc" _ (ERR AUTH) <- signSendRecv h badKey ("dabc", EntityId "1234", cmd)
|
|
return ()
|
|
let ok = similarTime timeNoQueue timeWrongKey msType
|
|
unless ok . putStrLn . unwords $
|
|
[ show goodKeyAlg,
|
|
show badKeyAlg,
|
|
show timeWrongKey,
|
|
show timeNoQueue,
|
|
show $ timeWrongKey / timeNoQueue - 1
|
|
]
|
|
ok `shouldBe` True
|
|
|
|
testMessageNotifications :: SpecWith (ASrvTransport, AStoreType)
|
|
testMessageNotifications =
|
|
it "should create simplex connection, subscribe notifier and deliver notifications" $ \(ATransport t, msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
smpTest4 t msType $ \rh sh nh1 nh2 -> do
|
|
(sId, rId, rKey, dhShared) <- createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
(nPub, nKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(rcvNtfPubDhKey, _) <- atomically $ C.generateKeyPair g
|
|
Resp "1" _ (NID nId' _) <- signSendRecv rh rKey ("1", rId, NKEY nPub rcvNtfPubDhKey)
|
|
Resp "1a" _ (NID nId _) <- signSendRecv rh rKey ("1a", rId, NKEY nPub rcvNtfPubDhKey)
|
|
nId' `shouldNotBe` nId
|
|
-- can't subscribe with service signature without service connection
|
|
(_, servicePK) <- atomically $ C.generateKeyPair g
|
|
Resp "2'" _ (ERR SERVICE) <- serviceSignSendRecv nh1 nKey servicePK ("2'", nId, NSUB)
|
|
Resp "2" _ (SOK Nothing) <- signSendRecv nh1 nKey ("2", nId, NSUB)
|
|
Resp "3" _ OK <- signSendRecv sh sKey ("3", sId, _SEND' "hello")
|
|
Resp "" _ (Msg mId1 msg1) <- tGet1 rh
|
|
(dec mId1 msg1, Right "hello") #== "delivered from queue"
|
|
Resp "3a" _ OK <- signSendRecv rh rKey ("3a", rId, ACK mId1)
|
|
Resp "" _ (NMSG _ _) <- tGet1 nh1
|
|
Resp "4" _ (SOK Nothing) <- signSendRecv nh2 nKey ("4", nId, NSUB)
|
|
Resp "" nId2 END <- tGet1 nh1
|
|
nId2 `shouldBe` nId
|
|
Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, _SEND' "hello again")
|
|
Resp "" _ (Msg mId2 msg2) <- tGet1 rh
|
|
Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2)
|
|
(dec mId2 msg2, Right "hello again") #== "delivered from queue again"
|
|
Resp "" _ (NMSG _ _) <- tGet1 nh2
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg nh1 >>= \case
|
|
Nothing -> pure ()
|
|
Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection"
|
|
Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL)
|
|
Resp "" nId3 DELD <- tGet1 nh2
|
|
nId3 `shouldBe` nId
|
|
Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there")
|
|
Resp "" _ (Msg mId3 msg3) <- tGet1 rh
|
|
(dec mId3 msg3, Right "hello there") #== "delivered from queue again"
|
|
Resp "7a" _ OK <- signSendRecv rh rKey ("7a", rId, ACK mId3)
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg nh2 >>= \case
|
|
Nothing -> pure ()
|
|
Just _ -> error "nothing else should be delivered to the 2nd notifier's TCP connection"
|
|
(nPub'', nKey'') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(rcvNtfPubDhKey'', _) <- atomically $ C.generateKeyPair g
|
|
Resp "8" _ (NID nId'' _) <- signSendRecv rh rKey ("8", rId, NKEY nPub'' rcvNtfPubDhKey'')
|
|
Resp "9" _ (SOK Nothing) <- signSendRecv nh1 nKey'' ("9", nId'', NSUB)
|
|
Resp "10" _ OK <- signSendRecv sh sKey ("10", sId, _SEND' "one more")
|
|
Resp "" _ (Msg mId4 msg4) <- tGet1 rh
|
|
(dec mId4 msg4, Right "one more") #== "delivered from queue"
|
|
Resp "10a" _ OK <- signSendRecv rh rKey ("10a", rId, ACK mId4)
|
|
Resp "" _ (NMSG _ _) <- tGet1 nh1
|
|
pure ()
|
|
|
|
testMessageServiceNotifications :: SpecWith (ASrvTransport, AStoreType)
|
|
testMessageServiceNotifications =
|
|
it "should create simplex connection, subscribe notifier as service and deliver notifications" $ \(ATransport t, msType) -> do
|
|
g <- C.newRandom
|
|
smpTest2 t msType $ \rh sh -> do
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(sId, rId, rKey, dhShared) <- createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
(nPub, nKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(rcvNtfPubDhKey, _) <- atomically $ C.generateKeyPair g
|
|
Resp "1" _ (NID nId _) <- signSendRecv rh rKey ("1", rId, NKEY nPub rcvNtfPubDhKey)
|
|
serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
|
|
testNtfServiceClient t serviceKeys $ \nh1 -> do
|
|
-- can't subscribe without service signature in service connection
|
|
Resp "2a" _ (ERR SERVICE) <- signSendRecv nh1 nKey ("2a", nId, NSUB)
|
|
Resp "2b" _ (SOK (Just serviceId)) <- serviceSignSendRecv nh1 nKey servicePK ("2b", nId, NSUB)
|
|
-- repeat subscription works, to support retries
|
|
Resp "2c" _ (SOK (Just serviceId'')) <- serviceSignSendRecv nh1 nKey servicePK ("2c", nId, NSUB)
|
|
serviceId'' `shouldBe` serviceId
|
|
deliverMessage rh rId rKey sh sId sKey nh1 "hello" dec
|
|
testNtfServiceClient t serviceKeys $ \nh2 -> do
|
|
Resp "4" _ (SOK (Just serviceId')) <- serviceSignSendRecv nh2 nKey servicePK ("4", nId, NSUB)
|
|
serviceId' `shouldBe` serviceId
|
|
-- service subscription is terminated
|
|
Resp "" serviceId2 (ENDS 1) <- tGet1 nh1
|
|
serviceId2 `shouldBe` serviceId
|
|
deliverMessage rh rId rKey sh sId sKey nh2 "hello again" dec
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg nh1 >>= \case
|
|
Nothing -> pure ()
|
|
Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection"
|
|
Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL)
|
|
Resp "" nId3 DELD <- tGet1 nh2
|
|
nId3 `shouldBe` nId
|
|
Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there")
|
|
Resp "" _ (Msg mId3 msg3) <- tGet1 rh
|
|
(dec mId3 msg3, Right "hello there") #== "delivered from queue again"
|
|
Resp "7a" _ OK <- signSendRecv rh rKey ("7a", rId, ACK mId3)
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg nh2 >>= \case
|
|
Nothing -> pure ()
|
|
Just _ -> error "nothing else should be delivered to the 2nd notifier's TCP connection"
|
|
-- new notification credentials
|
|
(nPub', nKey') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(rcvNtfPubDhKey', _) <- atomically $ C.generateKeyPair g
|
|
Resp "8" _ (NID nId' _) <- signSendRecv rh rKey ("8", rId, NKEY nPub' rcvNtfPubDhKey')
|
|
nId' == nId `shouldBe` False
|
|
Resp "9" _ (SOK (Just serviceId3)) <- serviceSignSendRecv nh2 nKey' servicePK ("9", nId', NSUB)
|
|
serviceId3 `shouldBe` serviceId
|
|
-- another queue
|
|
(sPub'', sKey'') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(sId'', rId'', rKey'', dhShared'') <- createAndSecureQueue rh sPub''
|
|
let dec'' = decryptMsgV3 dhShared''
|
|
(nPub'', nKey'') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(rcvNtfPubDhKey'', _) <- atomically $ C.generateKeyPair g
|
|
Resp "10" _ (NID nId'' _) <- signSendRecv rh rKey'' ("10", rId'', NKEY nPub'' rcvNtfPubDhKey'')
|
|
nId'' == nId `shouldBe` False
|
|
Resp "11" _ (SOK (Just serviceId4)) <- serviceSignSendRecv nh2 nKey'' servicePK ("11", nId'', NSUB)
|
|
serviceId4 `shouldBe` serviceId
|
|
deliverMessage rh rId rKey sh sId sKey nh2 "connection 1" dec
|
|
deliverMessage rh rId'' rKey'' sh sId'' sKey'' nh2 "connection 2" dec''
|
|
-- -- another client makes service subscription
|
|
Resp "12" serviceId5 (SOKS 2) <- signSendRecv nh1 (C.APrivateAuthKey C.SEd25519 servicePK) ("12", serviceId, NSUBS)
|
|
serviceId5 `shouldBe` serviceId
|
|
Resp "" serviceId6 (ENDS 2) <- tGet1 nh2
|
|
serviceId6 `shouldBe` serviceId
|
|
deliverMessage rh rId rKey sh sId sKey nh1 "connection 1 one more" dec
|
|
deliverMessage rh rId'' rKey'' sh sId'' sKey'' nh1 "connection 2 one more" dec''
|
|
where
|
|
deliverMessage rh rId rKey sh sId sKey nh msgText dec = do
|
|
Resp "msg-1" _ OK <- signSendRecv sh sKey ("msg-1", sId, _SEND' msgText)
|
|
Resp "" _ (Msg mId msg) <- tGet1 rh
|
|
Resp "msg-2" _ OK <- signSendRecv rh rKey ("msg-2", rId, ACK mId)
|
|
(dec mId msg, Right msgText) #== "delivered from queue"
|
|
Resp "" _ (NMSG _ _) <- tGet1 nh
|
|
pure ()
|
|
|
|
testServiceNotificationsTwoRestarts :: SpecWith (ASrvTransport, AStoreType)
|
|
testServiceNotificationsTwoRestarts =
|
|
it "subscribe notifier as service and deliver notifications after two restarts" $ \ps@(ATransport t, _) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(nPub, nKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
|
|
(rcvNtfPubDhKey, _) <- atomically $ C.generateKeyPair g
|
|
(rId, rKey, sId, dec, serviceId) <- withSmpServerStoreLogOn ps testPort $ runTest2 t $ \sh rh -> do
|
|
(sId, rId, rKey, dhShared) <- createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
Resp "0" _ (NID nId _) <- signSendRecv rh rKey ("0", rId, NKEY nPub rcvNtfPubDhKey)
|
|
testNtfServiceClient t serviceKeys $ \nh -> do
|
|
Resp "1" _ (SOK (Just serviceId)) <- serviceSignSendRecv nh nKey servicePK ("1", nId, NSUB)
|
|
deliverMessage rh rId rKey sh sId sKey nh "hello" dec
|
|
pure (rId, rKey, sId, dec, serviceId)
|
|
threadDelay 250000
|
|
withSmpServerStoreLogOn ps testPort $ runTest2 t $ \sh rh ->
|
|
testNtfServiceClient t serviceKeys $ \nh -> do
|
|
Resp "2.1" serviceId' (SOKS n) <- signSendRecv nh (C.APrivateAuthKey C.SEd25519 servicePK) ("2.1", serviceId, NSUBS)
|
|
n `shouldBe` 1
|
|
Resp "2.2" _ (SOK Nothing) <- signSendRecv rh rKey ("2.2", rId, SUB)
|
|
serviceId' `shouldBe` serviceId
|
|
deliverMessage rh rId rKey sh sId sKey nh "hello 2" dec
|
|
threadDelay 250000
|
|
withSmpServerStoreLogOn ps testPort $ runTest2 t $ \sh rh ->
|
|
testNtfServiceClient t serviceKeys $ \nh -> do
|
|
Resp "3.1" _ (SOKS n) <- signSendRecv nh (C.APrivateAuthKey C.SEd25519 servicePK) ("3.1", serviceId, NSUBS)
|
|
n `shouldBe` 1
|
|
Resp "3.2" _ (SOK Nothing) <- signSendRecv rh rKey ("3.2", rId, SUB)
|
|
deliverMessage rh rId rKey sh sId sKey nh "hello 3" dec
|
|
where
|
|
runTest2 :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO a) -> ThreadId -> IO a
|
|
runTest2 _ test' server = do
|
|
a <- testSMPClient $ \h1 -> testSMPClient $ \h2 -> test' h1 h2
|
|
killThread server
|
|
pure a
|
|
deliverMessage rh rId rKey sh sId sKey nh msgText dec = do
|
|
Resp "msg-1" _ OK <- signSendRecv sh sKey ("msg-1", sId, _SEND' msgText)
|
|
Resp "" _ (Msg mId msg) <- tGet1 rh
|
|
Resp "msg-2" _ OK <- signSendRecv rh rKey ("msg-2", rId, ACK mId)
|
|
(dec mId msg, Right msgText) #== "delivered from queue"
|
|
Resp "" _ (NMSG _ _) <- tGet1 nh
|
|
pure ()
|
|
|
|
testMsgExpireOnSend :: SpecWith (ASrvTransport, AStoreType)
|
|
testMsgExpireOnSend =
|
|
it "should expire messages that are not received before messageTTL on SEND" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
let cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {expireMessagesOnSend = True, messageExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 10000}}
|
|
withSmpServerConfigOn (ATransport t) cfg' testPort $ \_ ->
|
|
testSMPClient @c $ \sh -> do
|
|
(sId, rId, rKey, dhShared) <- testSMPClient @c $ \rh -> createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
Resp "1" _ OK <- signSendRecv sh sKey ("1", sId, _SEND "hello (should expire)")
|
|
threadDelay 2500000
|
|
Resp "2" _ OK <- signSendRecv sh sKey ("2", sId, _SEND "hello (should NOT expire)")
|
|
testSMPClient @c $ \rh -> do
|
|
(Resp "3" _ (SOK Nothing), Resp "" _ (Msg mId msg)) <- signSendRecv2 rh rKey ("3", rId, SUB)
|
|
(dec mId msg, Right "hello (should NOT expire)") #== "delivered"
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg rh >>= \case
|
|
Nothing -> return ()
|
|
Just _ -> error "nothing else should be delivered"
|
|
|
|
testMsgExpireOnInterval :: SpecWith (ASrvTransport, AStoreType)
|
|
testMsgExpireOnInterval =
|
|
it "should expire messages that are not received before messageTTL after expiry interval" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
let cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {messageExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 1}, idleQueueInterval = 1}
|
|
withSmpServerConfigOn (ATransport t) cfg' testPort $ \_ ->
|
|
testSMPClient @c $ \sh -> do
|
|
(sId, rId, rKey, _) <- testSMPClient @c $ \rh -> createAndSecureQueue rh sPub
|
|
Resp "1" _ OK <- signSendRecv sh sKey ("1", sId, _SEND "hello (should expire)")
|
|
threadDelay 3000000
|
|
testSMPClient @c $ \rh -> do
|
|
signSendRecv rh rKey ("2", rId, SUB) >>= \case
|
|
Resp "2" _ (SOK Nothing) -> pure ()
|
|
r -> unexpected r
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg rh >>= \case
|
|
Nothing -> return ()
|
|
Just _ -> error "nothing should be delivered"
|
|
|
|
testMsgNOTExpireOnInterval :: SpecWith (ASrvTransport, AStoreType)
|
|
testMsgNOTExpireOnInterval =
|
|
it "should block and unblock message queues" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do
|
|
g <- C.newRandom
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
let cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {messageExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 10000}}
|
|
withSmpServerConfigOn (ATransport t) cfg' testPort $ \_ ->
|
|
testSMPClient @c $ \sh -> do
|
|
(sId, rId, rKey, dhShared) <- testSMPClient @c $ \rh -> createAndSecureQueue rh sPub
|
|
let dec = decryptMsgV3 dhShared
|
|
Resp "1" _ OK <- signSendRecv sh sKey ("1", sId, _SEND "hello (should NOT expire)")
|
|
threadDelay 2500000
|
|
testSMPClient @c $ \rh -> do
|
|
(Resp "2" _ (SOK Nothing), Resp "" _ (Msg mId msg)) <- signSendRecv2 rh rKey ("2", rId, SUB)
|
|
(dec mId msg, Right "hello (should NOT expire)") #== "delivered"
|
|
1000 `timeout` tGetClient @SMPVersion @ErrorType @BrokerMsg rh >>= \case
|
|
Nothing -> return ()
|
|
Just _ -> error "nothing else should be delivered"
|
|
|
|
testBlockMessageQueue :: SpecWith (ASrvTransport, AStoreType)
|
|
testBlockMessageQueue =
|
|
-- TODO [postgres]
|
|
xit "should return BLOCKED error when queue is blocked" $ \ps@(ATransport (t :: TProxy c 'TServer), _) -> do
|
|
g <- C.newRandom
|
|
(rId, sId) <- withSmpServerStoreLogOn ps testPort $ runTest t $ \h -> do
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
(dhPub, _dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
Resp "abcd" rId1 (Ids rId sId _srvDh) <- signSendRecv h rKey ("abcd", NoEntity, New rPub dhPub)
|
|
(rId1, NoEntity) #== "creates queue"
|
|
pure (rId, sId)
|
|
|
|
-- TODO [postgres] block via control port
|
|
withFile testStoreLogFile AppendMode $ \h -> B.hPutStrLn h $ strEncode $ BlockQueue rId $ BlockingInfo BRContent Nothing
|
|
|
|
withSmpServerStoreLogOn ps testPort $ runTest t $ \h -> do
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
|
Resp "dabc" sId2 (ERR (BLOCKED (BlockingInfo BRContent Nothing))) <- signSendRecv h sKey ("dabc", sId, SKEY sPub)
|
|
(sId2, sId) #== "same queue ID in response"
|
|
where
|
|
runTest :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO a) -> ThreadId -> IO a
|
|
runTest _ test' server = do
|
|
a <- testSMPClient test'
|
|
killThread server
|
|
pure a
|
|
|
|
testInvQueueLinkData :: SpecWith (ASrvTransport, AStoreType)
|
|
testInvQueueLinkData =
|
|
it "create and access queue short link data for 1-time invitation" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \r s -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(dhPub, _dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
C.CbNonce corrId <- atomically $ C.randomCbNonce g
|
|
let sId = EntityId $ B.take 24 $ C.sha3_384 corrId
|
|
ld = (EncDataBytes "fixed data", EncDataBytes "user data")
|
|
qrd = QRMessaging $ Just (sId, ld)
|
|
-- sender ID must be derived from corrId
|
|
Resp "1" NoEntity (ERR (CMD PROHIBITED)) <-
|
|
signSendRecv r rKey ("1", NoEntity, NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just qrd) Nothing))
|
|
Resp corrId' NoEntity (IDS (QIK rId sId' _srvDh (Just QMMessaging) (Just lnkId) Nothing Nothing)) <-
|
|
signSendRecv r rKey (corrId, NoEntity, NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just qrd) Nothing))
|
|
(sId', sId) #== "should return the same sender ID"
|
|
corrId' `shouldBe` CorrId corrId
|
|
-- can't read link data with LGET
|
|
Resp "2" lnkId' (ERR AUTH) <- sendRecv s ("", "2", lnkId, LGET)
|
|
lnkId' `shouldBe` lnkId
|
|
-- can update link data before it is secured
|
|
let newLD = (EncDataBytes "fixed data", EncDataBytes "updated user data")
|
|
Resp "2a" rId' OK <- signSendRecv r rKey ("2a", rId, LSET lnkId newLD)
|
|
rId' `shouldBe` rId
|
|
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
|
|
Resp "3a" _ err2 <- sendRecv s ("", "3a", lnkId, LKEY sPub)
|
|
(err2, ERR (CMD NO_AUTH)) #== "rejects LKEY without signature"
|
|
|
|
Resp "3b" _ err2' <- sendRecv s (sampleSig, "3b", lnkId, LKEY sPub)
|
|
(err2', ERR AUTH) #== "rejects LKEY with wrong signature"
|
|
|
|
Resp "4" _ err3 <- signSendRecv s sKey ("4", rId, LKEY sPub)
|
|
(err3, ERR AUTH) #== "rejects LKEY with recipients's ID"
|
|
|
|
Resp "5" lnkId2 (LNK sId2 ld') <- signSendRecv s sKey ("5", lnkId, LKEY sPub)
|
|
(lnkId2, lnkId) #== "secures queue and returns link data, same link ID in response"
|
|
(sId2, sId) #== "same sender ID in response"
|
|
(ld', newLD) #== "returns updated stored data"
|
|
|
|
(sPub', sKey') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
Resp "6" _ err4 <- signSendRecv s sKey' ("6", lnkId, LKEY sPub')
|
|
(err4, ERR AUTH) #== "rejects if secured with different key"
|
|
|
|
Resp "7" _ (LNK sId3 ld2) <- signSendRecv s sKey ("7", lnkId, LKEY sPub)
|
|
sId3 `shouldBe` sId
|
|
ld2 `shouldBe` newLD
|
|
|
|
Resp "8" rId2 (ERR AUTH) <- signSendRecv r rKey ("8", rId, LSET lnkId newLD)
|
|
rId2 `shouldBe` rId
|
|
|
|
Resp "9" rId3 OK <- signSendRecv r rKey ("9", rId, LDEL)
|
|
rId3 `shouldBe` rId
|
|
|
|
testContactQueueLinkData :: SpecWith (ASrvTransport, AStoreType)
|
|
testContactQueueLinkData =
|
|
it "create and access queue short link data for contact address" $ \(ATransport t, msType) ->
|
|
smpTest2 t msType $ \r s -> do
|
|
g <- C.newRandom
|
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
(dhPub, _dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
|
C.CbNonce corrId <- atomically $ C.randomCbNonce g
|
|
lnkId <- EntityId <$> atomically (C.randomBytes 24 g)
|
|
let sId = EntityId $ B.take 24 $ C.sha3_384 corrId
|
|
ld = (EncDataBytes "fixed data", EncDataBytes "user data")
|
|
qrd = QRContact $ Just (lnkId, (sId, ld))
|
|
-- sender ID must be derived from corrId
|
|
Resp "1" NoEntity (ERR (CMD PROHIBITED)) <-
|
|
signSendRecv r rKey ("1", NoEntity, NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just qrd) Nothing))
|
|
Resp corrId' NoEntity (IDS (QIK rId sId' _srvDh (Just QMContact) (Just lnkId') Nothing Nothing)) <-
|
|
signSendRecv r rKey (corrId, NoEntity, NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just qrd) Nothing))
|
|
(lnkId', lnkId) #== "should return the same link ID"
|
|
(sId', sId) #== "should return the same sender ID"
|
|
corrId' `shouldBe` CorrId corrId
|
|
-- can't secure queue and read link data with LKEY
|
|
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
|
Resp "2" _ (ERR AUTH) <- signSendRecv s sKey ("2", lnkId, LKEY sPub)
|
|
|
|
Resp "3" _ err2 <- sendRecv s (sampleSig, "3", lnkId, LGET)
|
|
(err2, ERR (CMD HAS_AUTH)) #== "rejects LGET with signature"
|
|
|
|
Resp "4" _ err3 <- sendRecv s ("", "4", rId, LGET)
|
|
(err3, ERR AUTH) #== "rejects LGET with recipients's ID"
|
|
|
|
Resp "5" lnkId2 (LNK sId2 ld') <- sendRecv s ("", "5", lnkId, LGET)
|
|
(lnkId2, lnkId) #== "returns link data, same link ID in response"
|
|
(sId2, sId) #== "same sender ID in response"
|
|
(ld', ld) #== "returns stored data"
|
|
|
|
Resp "6" _ (LNK sId3 ld2) <- sendRecv s ("", "6", lnkId, LGET)
|
|
sId3 `shouldBe` sId
|
|
ld2 `shouldBe` ld
|
|
|
|
let newLD = (EncDataBytes "fixed data", EncDataBytes "updated user data")
|
|
Resp "7" rId' OK <- signSendRecv r rKey ("7", rId, LSET lnkId newLD)
|
|
rId' `shouldBe` rId
|
|
|
|
Resp "8" _ (LNK sId4 ld3) <- sendRecv s ("", "8", lnkId, LGET)
|
|
sId4 `shouldBe` sId
|
|
ld3 `shouldBe` newLD
|
|
|
|
badLnkId <- EntityId <$> atomically (C.randomBytes 24 g)
|
|
Resp "9" _ (ERR AUTH) <- signSendRecv r rKey ("9", rId, LSET badLnkId newLD)
|
|
|
|
let badLD = (EncDataBytes "changed fixed data", EncDataBytes "updated user data 2")
|
|
Resp "10" _ (ERR AUTH) <- signSendRecv r rKey ("10", rId, LSET lnkId badLD)
|
|
|
|
Resp "11" rId2 OK <- signSendRecv r rKey ("11", rId, LDEL)
|
|
rId2 `shouldBe` rId
|
|
Resp "11a" _ OK <- signSendRecv r rKey ("11a", rId, LDEL)
|
|
|
|
Resp "12" lnkId3 (ERR AUTH) <- sendRecv s ("", "12", lnkId, LGET)
|
|
lnkId3 `shouldBe` lnkId
|
|
|
|
samplePubKey :: C.APublicVerifyKey
|
|
samplePubKey = C.APublicVerifyKey C.SEd25519 "MCowBQYDK2VwAyEAfAOflyvbJv1fszgzkQ6buiZJVgSpQWsucXq7U6zjMgY="
|
|
|
|
sampleDhPubKey :: C.PublicKey 'C.X25519
|
|
sampleDhPubKey = "MCowBQYDK2VuAyEAriy+HcARIhqsgSjVnjKqoft+y6pxrxdY68zn4+LjYhQ="
|
|
|
|
sampleSig :: Maybe TAuthorizations
|
|
sampleSig = Just (TASignature "e8JK+8V3fq6kOLqco/SaKlpNaQ7i1gfOrXoqekEl42u4mF8Bgu14T5j0189CGcUhJHw2RwCMvON+qbvQ9ecJAA==", Nothing)
|
|
|
|
noAuth :: (Char, Maybe BasicAuth)
|
|
noAuth = ('A', Nothing)
|
|
|
|
deriving instance Eq TransmissionAuth
|
|
|
|
instance Eq C.ASignature where
|
|
C.ASignature a s == C.ASignature a' s' = case testEquality a a' of
|
|
Just Refl -> s == s'
|
|
_ -> False
|
|
|
|
instance IsString (Maybe TAuthorizations) where
|
|
fromString = parseString $ B64.decode >=> C.decodeSignature >=> pure . fmap ((,Nothing) . TASignature)
|
|
|
|
serverSyntaxTests :: ASrvTransport -> Spec
|
|
serverSyntaxTests (ATransport t) = do
|
|
it "unknown command" $ ("", "abcd", "1234", ('H', 'E', 'L', 'L', 'O')) >#> ("", "abcd", "1234", ERR $ CMD UNKNOWN)
|
|
describe "NEW" $ do
|
|
it "no parameters" $ (sampleSig, "bcda", "", NEW_) >#> ("", "bcda", "", ERR $ CMD SYNTAX)
|
|
it "many parameters" $ (sampleSig, "cdab", "", (NEW_, ' ', ('\x01', 'A'), samplePubKey, sampleDhPubKey)) >#> ("", "cdab", "", ERR $ CMD SYNTAX)
|
|
it "no signature" $ ("", "dabc", "", ((NEW_, ' ', samplePubKey, sampleDhPubKey), ('0', SMSubscribe, Just (QRMessaging Nothing), '0'))) >#> ("", "dabc", "", ERR $ CMD NO_AUTH)
|
|
it "queue ID" $ (sampleSig, "abcd", "12345678", ((NEW_, ' ', samplePubKey, sampleDhPubKey), ('0', SMSubscribe, Just (QRMessaging Nothing), '0'))) >#> ("", "abcd", "12345678", ERR $ CMD HAS_AUTH)
|
|
describe "KEY" $ do
|
|
it "valid syntax" $ (sampleSig, "bcda", "12345678", (KEY_, ' ', samplePubKey)) >#> ("", "bcda", "12345678", ERR AUTH)
|
|
it "no parameters" $ (sampleSig, "cdab", "12345678", KEY_) >#> ("", "cdab", "12345678", ERR $ CMD SYNTAX)
|
|
it "many parameters" $ (sampleSig, "dabc", "12345678", (KEY_, ' ', ('\x01', 'A'), samplePubKey)) >#> ("", "dabc", "12345678", ERR $ CMD SYNTAX)
|
|
it "no signature" $ ("", "abcd", "12345678", (KEY_, ' ', samplePubKey)) >#> ("", "abcd", "12345678", ERR $ CMD NO_AUTH)
|
|
it "no queue ID" $ (sampleSig, "bcda", "", (KEY_, ' ', samplePubKey)) >#> ("", "bcda", "", ERR $ CMD NO_AUTH)
|
|
noParamsSyntaxTest "SUB" SUB_
|
|
noParamsSyntaxTest "OFF" OFF_
|
|
noParamsSyntaxTest "DEL" DEL_
|
|
describe "SEND" $ do
|
|
it "valid syntax" $ (sampleSig, "cdab", "12345678", (SEND_, ' ', noMsgFlags, ' ', "hello" :: ByteString)) >#> ("", "cdab", "12345678", ERR AUTH)
|
|
it "no parameters" $ (sampleSig, "abcd", "12345678", SEND_) >#> ("", "abcd", "12345678", ERR $ CMD SYNTAX)
|
|
it "no queue ID" $ (sampleSig, "bcda", "", (SEND_, ' ', noMsgFlags, ' ', "hello" :: ByteString)) >#> ("", "bcda", "", ERR $ CMD NO_ENTITY)
|
|
describe "ACK" $ do
|
|
it "valid syntax" $ (sampleSig, "cdab", "12345678", (ACK_, ' ', "1234" :: ByteString)) >#> ("", "cdab", "12345678", ERR AUTH)
|
|
it "no parameters" $ (sampleSig, "abcd", "12345678", ACK_) >#> ("", "abcd", "12345678", ERR $ CMD SYNTAX)
|
|
it "no queue ID" $ (sampleSig, "bcda", "", (ACK_, ' ', "1234" :: ByteString)) >#> ("", "bcda", "", ERR $ CMD NO_AUTH)
|
|
it "no signature" $ ("", "cdab", "12345678", (ACK_, ' ', "1234" :: ByteString)) >#> ("", "cdab", "12345678", ERR $ CMD NO_AUTH)
|
|
describe "PING" $ do
|
|
it "valid syntax" $ ("", "abcd", "", PING_) >#> ("", "abcd", "", PONG)
|
|
describe "broker response not allowed" $ do
|
|
it "OK" $ (sampleSig, "bcda", "12345678", OK_) >#> ("", "bcda", "12345678", ERR $ CMD UNKNOWN)
|
|
where
|
|
noParamsSyntaxTest :: PartyI p => String -> CommandTag p -> Spec
|
|
noParamsSyntaxTest description cmd = describe description $ do
|
|
it "valid syntax" $ (sampleSig, "abcd", "12345678", cmd) >#> ("", "abcd", "12345678", ERR AUTH)
|
|
it "wrong terminator" $ (sampleSig, "bcda", "12345678", (cmd, '=')) >#> ("", "bcda", "12345678", ERR $ CMD UNKNOWN)
|
|
it "no signature" $ ("", "cdab", "12345678", cmd) >#> ("", "cdab", "12345678", ERR $ CMD NO_AUTH)
|
|
it "no queue ID" $ (sampleSig, "dabc", "", cmd) >#> ("", "dabc", "", ERR $ CMD NO_AUTH)
|
|
(>#>) ::
|
|
Encoding smp =>
|
|
(Maybe TAuthorizations, ByteString, ByteString, smp) ->
|
|
(Maybe TAuthorizations, ByteString, ByteString, BrokerMsg) ->
|
|
Expectation
|
|
command >#> response = withFrozenCallStack $ smpServerTest t command `shouldReturn` response
|