{-# 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.Foldable (foldrM)
import Data.Hashable (hash)
import qualified Data.IntSet as IS
import Data.List.NonEmpty (NonEmpty)
import Data.List (isPrefixOf)
import Data.Maybe (catMaybes)
import Data.String (IsString (..))
import Text.Read (readMaybe)
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.Transport.Credentials
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 "Service message subscriptions" $ do
    testServiceDeliverSubscribe
    testServiceUpgradeAndDowngrade
    testServiceSubsTotalCount
  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
    testDuplicateQueueLinkData

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 Ids_ :: RecipientId -> SenderId -> RcvPublicDhKey -> ServiceId -> BrokerMsg
pattern Ids_ rId sId srvDh serviceId <- IDS (QIK rId sId srvDh _sndSecure _linkId (Just serviceId) 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

serviceSignSendRecv2 :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO (Transmission (Either ErrorType BrokerMsg), Transmission (Either ErrorType BrokerMsg))
serviceSignSendRecv2 h pk serviceKey t = do
  [r1, r2] <- signSendRecv_ h pk (Just serviceKey) t
  pure (r1, r2)

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 pk serviceKey_ t = do
  signSend_ h pk serviceKey_ t
  tGetClient h

signSend_ :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> Maybe C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO ()
signSend_ 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)
  pure ()
  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 ()

testServiceDeliverSubscribe :: SpecWith (ASrvTransport, AStoreType)
testServiceDeliverSubscribe =
  it "should create queue as service and subscribe with SUBS after reconnect" $ \(at@(ATransport t), msType) -> do
    g <- C.newRandom
    creds <- genCredentials g Nothing (0, 2400) "localhost"
    let (_fp, tlsCred) = tlsCredentials [creds]
    serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
    let aServicePK = C.APrivateAuthKey C.SEd25519 servicePK
    withSmpServerConfigOn at (cfgMS msType) testPort $ \_ -> runSMPClient t $ \h -> do
      (rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      (sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g

      (rId, sId, dec, serviceId) <- runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        Resp "1" NoEntity (ERR SERVICE) <- signSendRecv sh rKey ("1", NoEntity, New rPub dhPub)
        Resp "2" NoEntity (Ids_ rId sId srvDh serviceId) <- serviceSignSendRecv sh rKey servicePK ("2", NoEntity, New rPub dhPub)
        let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
        Resp "3" sId' OK <- signSendRecv h sKey ("3", sId, SKEY sPub)
        sId' `shouldBe` sId
        Resp "4" _ OK <- signSendRecv h sKey ("4", sId, _SEND "hello")
        Resp "5" _ OK <- signSendRecv h sKey ("5", sId, _SEND "hello 2")
        Resp "" rId' (Msg mId1 msg1) <- tGet1 sh
        rId' `shouldBe` rId
        dec mId1 msg1 `shouldBe` Right "hello"
        -- ACK doesn't need service signature
        Resp "6" _ (Msg mId2 msg2) <- signSendRecv sh rKey ("6", rId, ACK mId1)
        dec mId2 msg2 `shouldBe` Right "hello 2"
        Resp "7" _ (ERR NO_MSG) <- signSendRecv sh rKey ("7", rId, ACK mId1)
        Resp "8" _ OK <- signSendRecv sh rKey ("8", rId, ACK mId2)
        Resp "9" _ OK <- signSendRecv h sKey ("9", sId, _SEND "hello 3")
        pure (rId, sId, dec, serviceId)

      runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        let idsHash = queueIdsHash [rId]
        Resp "10" NoEntity (ERR (CMD NO_AUTH)) <- signSendRecv sh aServicePK ("10", NoEntity, SUBS 1 idsHash)
        signSend_ sh aServicePK Nothing ("11", serviceId, SUBS 1 idsHash)
        [mId3] <-
          fmap catMaybes $
            receiveInAnyOrder -- race between SOKS, MSG and ALLS (sndQ and msgQ are separate threads)
              sh
              [ \case
                  Resp "11" serviceId' (SOKS n idsHash') -> do
                    n `shouldBe` 1
                    idsHash' `shouldBe` idsHash
                    serviceId' `shouldBe` serviceId
                    pure $ Just Nothing
                  _ -> pure Nothing,
                \case
                  Resp "" rId'' (Msg mId3 msg3) -> do
                    rId'' `shouldBe` rId
                    dec mId3 msg3 `shouldBe` Right "hello 3"
                    pure $ Just $ Just mId3
                  _ -> pure Nothing,
                \case
                  Resp "" NoEntity ALLS -> pure $ Just Nothing
                  _ -> pure Nothing
              ]
        Resp "12" _ OK <- signSendRecv sh rKey ("12", rId, ACK mId3)
        Resp "14" _ OK <- signSendRecv h sKey ("14", sId, _SEND "hello 4")
        Resp "" _ (Msg mId4 msg4) <- tGet1 sh
        dec mId4 msg4 `shouldBe` Right "hello 4"
        Resp "15" _ OK <- signSendRecv sh rKey ("15", rId, ACK mId4)
        pure ()

testServiceUpgradeAndDowngrade :: SpecWith (ASrvTransport, AStoreType)
testServiceUpgradeAndDowngrade =
  it "should create queue as client and switch to service and back" $ \(at@(ATransport t), msType) -> do
    g <- C.newRandom
    creds <- genCredentials g Nothing (0, 2400) "localhost"
    let (_fp, tlsCred) = tlsCredentials [creds]
    serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
    let aServicePK = C.APrivateAuthKey C.SEd25519 servicePK
    withSmpServerConfigOn at (cfgMS msType) testPort $ \_ -> runSMPClient t $ \h -> do
      (rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      (sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (rPub2, rKey2) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub2, dhPriv2 :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      (sPub2, sKey2) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (rPub3, rKey3) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub3, dhPriv3 :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      (sPub3, sKey3) <- atomically $ C.generateAuthKeyPair C.SEd25519 g

      (rId, sId, dec) <- runSMPClient t $ \sh -> do
        Resp "1" NoEntity (Ids rId sId srvDh) <- signSendRecv sh rKey ("1", NoEntity, New rPub dhPub)
        let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
        Resp "2" sId' OK <- signSendRecv h sKey ("2", sId, SKEY sPub)
        sId' `shouldBe` sId
        Resp "3" _ OK <- signSendRecv h sKey ("3", sId, _SEND "hello")
        Resp "" rId' (Msg mId1 msg1) <- tGet1 sh
        rId' `shouldBe` rId
        dec mId1 msg1 `shouldBe` Right "hello"
        Resp "4" _ OK <- signSendRecv sh rKey ("4", rId, ACK mId1)
        Resp "5" _ OK <- signSendRecv h sKey ("5", sId, _SEND "hello 2")
        pure (rId, sId, dec)

      -- split to prevent message delivery
      (rId2, sId2, dec2) <- runSMPClient t $ \sh -> do
        Resp "6" NoEntity (Ids rId2 sId2 srvDh2) <- signSendRecv sh rKey2 ("6", NoEntity, New rPub2 dhPub2)
        let dec2 = decryptMsgV3 $ C.dh' srvDh2 dhPriv2
        Resp "7" sId2' OK <- signSendRecv h sKey2 ("7", sId2, SKEY sPub2)
        sId2' `shouldBe` sId2
        pure (rId2, sId2, dec2)

      (rId3, _sId3, _dec3) <- runSMPClient t $ \sh -> do
        Resp "6" NoEntity (Ids rId3 sId3 srvDh3) <- signSendRecv sh rKey3 ("6", NoEntity, New rPub3 dhPub3)
        let dec3 = decryptMsgV3 $ C.dh' srvDh3 dhPriv3
        Resp "7" sId3' OK <- signSendRecv h sKey3 ("7", sId3, SKEY sPub3)
        sId3' `shouldBe` sId3
        pure (rId3, sId3, dec3)

      serviceId <- runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        Resp "8" _ (ERR SERVICE) <- signSendRecv sh rKey ("8", rId, SUB)
        (Resp "9" rId' (SOK (Just serviceId)), Resp "" rId'' (Msg mId2 msg2)) <- serviceSignSendRecv2 sh rKey servicePK ("9", rId, SUB)
        rId' `shouldBe` rId
        rId'' `shouldBe` rId
        dec mId2 msg2 `shouldBe` Right "hello 2"
        (Resp "10" rId2' (SOK (Just serviceId'))) <- serviceSignSendRecv sh rKey2 servicePK ("10", rId2, SUB)
        rId2' `shouldBe` rId2
        serviceId' `shouldBe` serviceId
        Resp "10.1" _ OK <- signSendRecv sh rKey ("10.1", rId, ACK mId2)
        (Resp "10.2" rId3' (SOK (Just serviceId''))) <- serviceSignSendRecv sh rKey3 servicePK ("10.2", rId3, SUB)
        rId3' `shouldBe` rId3
        serviceId'' `shouldBe` serviceId
        pure serviceId

      Resp "11" _ OK <- signSendRecv h sKey ("11", sId, _SEND "hello 3.1")
      Resp "12" _ OK <- signSendRecv h sKey2 ("12", sId2, _SEND "hello 3.2")

      runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        let idsHash = queueIdsHash [rId, rId2, rId3]
        signSend_ sh aServicePK Nothing ("14", serviceId, SUBS 3 idsHash)
        [(rKey3_1, rId3_1, mId3_1), (rKey3_2, rId3_2, mId3_2)] <-
          fmap catMaybes $
            receiveInAnyOrder -- race between SOKS, MSG and ALLS (sndQ and msgQ are separate threads)
              sh
              [ \case
                  Resp "14" serviceId' (SOKS n idsHash') -> do
                    n `shouldBe` 3
                    idsHash' `shouldBe` idsHash
                    serviceId' `shouldBe` serviceId
                    pure $ Just Nothing
                  _ -> pure Nothing,
                \case
                  Resp "" rId'' (Msg mId3 msg3) | rId'' == rId -> do
                    dec mId3 msg3 `shouldBe` Right "hello 3.1"
                    pure $ Just $ Just (rKey, rId, mId3)
                  _ -> pure Nothing,
                \case
                  Resp "" rId'' (Msg mId3 msg3) | rId'' == rId2 -> do
                    dec2 mId3 msg3 `shouldBe` Right "hello 3.2"
                    pure $ Just $ Just (rKey2, rId2, mId3)
                  _ -> pure Nothing,
                \case
                  Resp "" NoEntity ALLS -> pure $ Just Nothing
                  _ -> pure Nothing
              ]
        Resp "15" _ OK <- signSendRecv sh rKey3_1 ("15", rId3_1, ACK mId3_1)
        Resp "16" _ OK <- signSendRecv sh rKey3_2 ("16", rId3_2, ACK mId3_2)
        pure ()

      Resp "17" _ OK <- signSendRecv h sKey ("17", sId, _SEND "hello 4")

      runSMPClient t $ \sh -> do
        Resp "18" _ (ERR SERVICE) <- signSendRecv sh aServicePK ("18", serviceId, SUBS 3 mempty)
        (Resp "19" rId' (SOK Nothing), Resp "" rId'' (Msg mId4 msg4)) <- signSendRecv2 sh rKey ("19", rId, SUB)
        rId' `shouldBe` rId
        rId'' `shouldBe` rId
        dec mId4 msg4 `shouldBe` Right "hello 4"
        Resp "20" _ OK <- signSendRecv sh rKey ("20", rId, ACK mId4)
        Resp "21" _ OK <- signSendRecv h sKey ("21", sId, _SEND "hello 5")
        Resp "" _ (Msg mId5 msg5) <- tGet1 sh
        dec mId5 msg5 `shouldBe` Right "hello 5"
        Resp "22" _ OK <- signSendRecv sh rKey ("22", rId, ACK mId5)

        Resp "23" rId2' (SOK Nothing) <- signSendRecv sh rKey2 ("23", rId2, SUB)
        rId2' `shouldBe` rId2
        Resp "24" _ OK <- signSendRecv h sKey ("24", sId, _SEND "hello 6")
        Resp "" _ (Msg mId6 msg6) <- tGet1 sh
        dec mId6 msg6 `shouldBe` Right "hello 6"
        Resp "25" _ OK <- signSendRecv sh rKey ("25", rId, ACK mId6)
        pure ()

testServiceSubsTotalCount :: SpecWith (ASrvTransport, AStoreType)
testServiceSubsTotalCount =
  it "should track totalServiceSubs correctly via SUBS and SUB" $ \(at@(ATransport t), msType) -> do
    g <- C.newRandom
    creds <- genCredentials g Nothing (0, 2400) "localhost"
    let (_fp, tlsCred) = tlsCredentials [creds]
    serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
    let aServicePK = C.APrivateAuthKey C.SEd25519 servicePK
        cfg' = updateCfg (cfgMS msType) $ \cfg_ -> cfg_ {prometheusInterval = Just 1}
    withSmpServerConfigOn at cfg' testPort $ \_ -> runSMPClient t $ \h -> do
      -- Phase 1: create 2 queues as service, reconnect with SUBS, check metric = 2
      (rPub1, rKey1) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub1, _ :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      (rPub2, rKey2) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub2, _ :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g

      (rId1, rId2, serviceId) <- runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        Resp "1" NoEntity (Ids_ rId1 _sId1 _srvDh1 serviceId) <- serviceSignSendRecv sh rKey1 servicePK ("1", NoEntity, New rPub1 dhPub1)
        Resp "2" NoEntity (Ids_ rId2 _sId2 _srvDh2 serviceId') <- serviceSignSendRecv sh rKey2 servicePK ("2", NoEntity, New rPub2 dhPub2)
        serviceId' `shouldBe` serviceId
        pure (rId1, rId2, serviceId)

      runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        let idsHash = queueIdsHash [rId1, rId2]
        signSend_ sh aServicePK Nothing ("3", serviceId, SUBS 2 idsHash)
        void $
          receiveInAnyOrder sh
            [ \case
                Resp "3" serviceId' (SOKS n idsHash') -> do
                  n `shouldBe` 2
                  idsHash' `shouldBe` idsHash
                  serviceId' `shouldBe` serviceId
                  pure $ Just ()
                _ -> pure Nothing,
              \case
                Resp "" NoEntity ALLS -> pure $ Just ()
                _ -> pure Nothing
            ]
        threadDelay 1500000
        readFile testPrometheusMetricsFile >>= \m -> readServiceSubsMetric m `shouldBe` Just 2

      -- Phase 2: associate 1 more queue via SUB, reconnect with SUBS 3, check metric = 3
      (rPub3, rKey3) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (dhPub3, _ :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      (sPub3, sKey3) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      Resp "4" NoEntity (Ids rId3 sId3 _) <- signSendRecv h rKey3 ("4", NoEntity, New rPub3 dhPub3)
      Resp "5" _ OK <- signSendRecv h sKey3 ("5", sId3, SKEY sPub3)

      runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        Resp "6" _ (SOK (Just serviceId')) <- serviceSignSendRecv sh rKey3 servicePK ("6", rId3, SUB)
        serviceId' `shouldBe` serviceId

      runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
        let idsHash = queueIdsHash [rId1, rId2, rId3]
        signSend_ sh aServicePK Nothing ("7", serviceId, SUBS 3 idsHash)
        void $
          receiveInAnyOrder sh
            [ \case
                Resp "7" serviceId' (SOKS n idsHash') -> do
                  n `shouldBe` 3
                  idsHash' `shouldBe` idsHash
                  serviceId' `shouldBe` serviceId
                  pure $ Just ()
                _ -> pure Nothing,
              \case
                Resp "" NoEntity ALLS -> pure $ Just ()
                _ -> pure Nothing
            ]
        threadDelay 1500000
        readFile testPrometheusMetricsFile >>= \m -> readServiceSubsMetric m `shouldBe` Just 3

readServiceSubsMetric :: String -> Maybe Int
readServiceSubsMetric content =
  case filter ("simplex_smp_subscribtion_service_subs_total " `isPrefixOf`) (lines content) of
    (line : _) -> case words line of
      [_, val, _] -> readMaybe val
      [_, val] -> readMaybe val
      _ -> Nothing
    [] -> Nothing

receiveInAnyOrder :: (HasCallStack, Transport c) => THandleSMP c 'TClient -> [(CorrId, EntityId, Either ErrorType BrokerMsg) -> IO (Maybe b)] -> IO [b]
receiveInAnyOrder h = fmap reverse . go []
  where
    go rs [] = pure rs
    go rs ps = withFrozenCallStack $ do
      r <- 5000000 `timeout` tGet1 h >>= maybe (error "inAnyOrder timeout") pure
      (r_, ps') <- foldrM (choose r) (Nothing, []) ps
      case r_ of
        Just r' -> go (r' : rs) ps'
        Nothing -> error $ "unexpected event: " <> show r
    choose r p (Nothing, ps') = (maybe (Nothing, p : ps') ((,ps') . Just)) <$> p r
    choose _ p (Just r, ps') = pure (Just r, p : ps')

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` 101
    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` 101
    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` 101
    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
          let idsHash = queueIdsHash [nId', nId'']
          Resp "12" serviceId5 (SOKS 2 idsHash') <- signSendRecv nh1 (C.APrivateAuthKey C.SEd25519 servicePK) ("12", serviceId, NSUBS 2 idsHash)
          idsHash' `shouldBe` idsHash
          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, nId, 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, nId, serviceId)
    let idsHash = queueIdsHash [nId]
    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 1 idsHash)
        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 1 idsHash)
        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

testDuplicateQueueLinkData :: SpecWith (ASrvTransport, AStoreType)
testDuplicateQueueLinkData =
  it "rejects attaching an existing short link to another queue" $ \(ATransport t, msType) ->
    smpTest2 t msType $ \r s -> do
      g <- C.newRandom
      (victimPub, victimKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (victimDhPub, _victimDhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      C.CbNonce corrId <- atomically $ C.randomCbNonce g
      lnkId <- EntityId <$> atomically (C.randomBytes 24 g)
      let victimSId = EntityId $ B.take 24 $ C.sha3_384 corrId
          victimLD = (EncDataBytes "fixed data", EncDataBytes "victim user data")
          victimQRD = QRContact $ Just (lnkId, (victimSId, victimLD))
          victimReq = NEW (NewQueueReq victimPub victimDhPub Nothing SMSubscribe (Just victimQRD) Nothing)
      Resp
        _
        NoEntity
        (IDS QIK {sndId = victimSId', queueMode = Just QMContact, linkId = Just lnkId'}) <-
        signSendRecv r victimKey (corrId, NoEntity, victimReq)
      lnkId' `shouldBe` lnkId
      victimSId' `shouldBe` victimSId

      Resp "1" lnkId1 (LNK victimSId1 victimLD1) <- sendRecv s ("", "1", lnkId, LGET)
      lnkId1 `shouldBe` lnkId
      victimSId1 `shouldBe` victimSId
      victimLD1 `shouldBe` victimLD

      (attackerPub, attackerKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
      (attackerDhPub, _attackerDhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
      let attackerReq =
            NEW (NewQueueReq attackerPub attackerDhPub Nothing SMSubscribe (Just $ QRContact Nothing) Nothing)
      Resp
        "2"
        NoEntity
        (IDS QIK {rcvId = attackerRId, queueMode = Just QMContact, linkId = Nothing}) <-
        signSendRecv r attackerKey ("2", NoEntity, attackerReq)

      let attackerLD = (EncDataBytes "fixed data", EncDataBytes "attacker user data")
      Resp "3" attackerRId' (ERR _) <-
        signSendRecv r attackerKey ("3", attackerRId, LSET lnkId attackerLD)
      attackerRId' `shouldBe` attackerRId

      Resp "4" lnkId2 (LNK victimSId2 victimLD2) <- sendRecv s ("", "4", lnkId, LGET)
      lnkId2 `shouldBe` lnkId
      victimSId2 `shouldBe` victimSId
      victimLD2 `shouldBe` victimLD

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
