From ffecd4a17af68677dedf05c95a80dc0f5c584236 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 24 May 2025 14:34:22 +0100 Subject: [PATCH] parameterize transport by peer type (client/server) (#1545) * parameterize transport by peer type (client/server) * LogDebug level when test is retried * support "flipped" HTTP2, fix test retry to avoid retrying pending tests * move sync to the end of the tests --- src/Simplex/FileTransfer/Transport.hs | 2 +- src/Simplex/Messaging/Client.hs | 8 +- src/Simplex/Messaging/Notifications/Server.hs | 6 +- .../Messaging/Notifications/Server/Env.hs | 4 +- .../Messaging/Notifications/Server/Main.hs | 4 +- .../Messaging/Notifications/Transport.hs | 8 +- src/Simplex/Messaging/Protocol.hs | 4 +- src/Simplex/Messaging/Server.hs | 21 ++-- src/Simplex/Messaging/Server/CLI.hs | 12 +- src/Simplex/Messaging/Server/Env/STM.hs | 4 +- src/Simplex/Messaging/Transport.hs | 116 ++++++++++-------- src/Simplex/Messaging/Transport/Client.hs | 7 +- src/Simplex/Messaging/Transport/HTTP2.hs | 4 +- .../Messaging/Transport/HTTP2/Client.hs | 29 +++-- .../Messaging/Transport/HTTP2/Server.hs | 6 +- src/Simplex/Messaging/Transport/Server.hs | 17 +-- src/Simplex/Messaging/Transport/WebSockets.hs | 66 +++++----- src/Simplex/RemoteControl/Client.hs | 14 +-- src/Simplex/RemoteControl/Discovery.hs | 4 +- src/Simplex/RemoteControl/Types.hs | 11 +- tests/AgentTests.hs | 4 +- tests/AgentTests/FunctionalAPITests.hs | 111 ++++++++--------- tests/AgentTests/NotificationTests.hs | 34 ++--- tests/CLITests.hs | 7 +- tests/NtfClient.hs | 10 +- tests/NtfServerTests.hs | 8 +- tests/SMPClient.hs | 37 +++--- tests/ServerTests.hs | 80 ++++++------ tests/Util.hs | 32 ++--- 29 files changed, 349 insertions(+), 321 deletions(-) diff --git a/src/Simplex/FileTransfer/Transport.hs b/src/Simplex/FileTransfer/Transport.hs index 3d80949d0..c94534b84 100644 --- a/src/Simplex/FileTransfer/Transport.hs +++ b/src/Simplex/FileTransfer/Transport.hs @@ -102,7 +102,7 @@ supportedFileServerVRange :: VersionRangeXFTP supportedFileServerVRange = mkVersionRange initialXFTPVersion currentXFTPVersion -- XFTP protocol does not use this handshake method -xftpClientHandshakeStub :: c -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeXFTP -> Bool -> ExceptT TransportError IO (THandle XFTPVersion c 'TClient) +xftpClientHandshakeStub :: c 'TClient -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeXFTP -> Bool -> ExceptT TransportError IO (THandle XFTPVersion c 'TClient) xftpClientHandshakeStub _c _ks _keyHash _xftpVRange _proxyServer = throwE TEVersion supportedXFTPhandshakes :: [ALPN] diff --git a/src/Simplex/Messaging/Client.hs b/src/Simplex/Messaging/Client.hs index 0dbf9c84f..2f600886b 100644 --- a/src/Simplex/Messaging/Client.hs +++ b/src/Simplex/Messaging/Client.hs @@ -424,7 +424,7 @@ data ProtocolClientConfig v = ProtocolClientConfig { -- | size of TBQueue to use for server commands and responses qSize :: Natural, -- | default server port if port is not specified in ProtocolServer - defaultTransport :: (ServiceName, ATransport), + defaultTransport :: (ServiceName, ATransport 'TClient), -- | network configuration networkConfig :: NetworkConfig, clientALPN :: Maybe [ALPN], @@ -553,7 +553,7 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize msgQ } - runClient :: (ServiceName, ATransport) -> TransportHost -> PClient v err msg -> IO (Either (ProtocolClientError err) (ProtocolClient v err msg)) + runClient :: (ServiceName, ATransport 'TClient) -> TransportHost -> PClient v err msg -> IO (Either (ProtocolClientError err) (ProtocolClient v err msg)) runClient (port', ATransport t) useHost c = do cVar <- newEmptyTMVarIO let tcConfig = (transportClientConfig networkConfig useHost useSNI) {alpn = clientALPN} @@ -567,7 +567,7 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize Just (Left e) -> pure $ Left e Nothing -> killThread tId $> Left PCENetworkError - useTransport :: (ServiceName, ATransport) + useTransport :: (ServiceName, ATransport 'TClient) useTransport = case port srv of "" -> case protocolTypeI @(ProtoType msg) of SPSMP | smpWebPort -> ("443", transport @TLS) @@ -581,7 +581,7 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize _ -> False SWPOff -> False - client :: forall c. Transport c => TProxy c -> PClient v err msg -> TMVar (Either (ProtocolClientError err) (ProtocolClient v err msg)) -> c -> IO () + client :: forall c. Transport c => TProxy c 'TClient -> PClient v err msg -> TMVar (Either (ProtocolClientError err) (ProtocolClient v err msg)) -> c 'TClient -> IO () client _ c cVar h = do ks <- if agreeSecret then Just <$> atomically (C.generateKeyPair g) else pure Nothing runExceptT (protocolClientHandshake @v @err @msg h ks (keyHash srv) serverVRange proxyServer) >>= \case diff --git a/src/Simplex/Messaging/Notifications/Server.hs b/src/Simplex/Messaging/Notifications/Server.hs index 0bbc30824..9fbe48a7f 100644 --- a/src/Simplex/Messaging/Notifications/Server.hs +++ b/src/Simplex/Messaging/Notifications/Server.hs @@ -71,7 +71,7 @@ import Simplex.Messaging.Server.QueueStore (getSystemDate) import Simplex.Messaging.Server.Stats (PeriodStats (..), PeriodStatCounts (..), periodStatCounts, periodStatDataCounts, updatePeriodStats) import Simplex.Messaging.Session import Simplex.Messaging.TMap (TMap) -import Simplex.Messaging.Transport (ATransport (..), THandle (..), THandleAuth (..), THandleParams (..), TProxy, Transport (..), TransportPeer (..), defaultSupportedParams) +import Simplex.Messaging.Transport (ASrvTransport, ATransport (..), THandle (..), THandleAuth (..), THandleParams (..), TProxy, Transport (..), TransportPeer (..), defaultSupportedParams) import Simplex.Messaging.Transport.Buffer (trimCR) import Simplex.Messaging.Transport.Server (AddHTTP, runTransportServer, runLocalTCPServer) import Simplex.Messaging.Util @@ -120,7 +120,7 @@ ntfServer cfg@NtfServerConfig {transports, transportConfig = tCfg, startOptions} ) `finally` stopServer where - runServer :: (ServiceName, ATransport, AddHTTP) -> M () + runServer :: (ServiceName, ASrvTransport, AddHTTP) -> M () runServer (tcpPort, ATransport t, _addHTTP) = do srvCreds <- asks tlsServerCreds serverSignKey <- either fail pure $ fromTLSCredentials srvCreds @@ -128,7 +128,7 @@ ntfServer cfg@NtfServerConfig {transports, transportConfig = tCfg, startOptions} liftIO $ runTransportServer started tcpPort defaultSupportedParams srvCreds (Just supportedNTFHandshakes) tCfg $ \h -> runClient serverSignKey t h `runReaderT` env fromTLSCredentials (_, pk) = C.x509ToPrivate (pk, []) >>= C.privKey - runClient :: Transport c => C.APrivateSignKey -> TProxy c -> c -> M () + runClient :: Transport c => C.APrivateSignKey -> TProxy c 'TServer -> c 'TServer -> M () runClient signKey _ h = do kh <- asks serverIdentity ks <- atomically . C.generateKeyPair =<< asks random diff --git a/src/Simplex/Messaging/Notifications/Server/Env.hs b/src/Simplex/Messaging/Notifications/Server/Env.hs index a287a065b..fbf8d5b4c 100644 --- a/src/Simplex/Messaging/Notifications/Server/Env.hs +++ b/src/Simplex/Messaging/Notifications/Server/Env.hs @@ -39,14 +39,14 @@ import Simplex.Messaging.Server.StoreLog (closeStoreLog) import Simplex.Messaging.Session import Simplex.Messaging.TMap (TMap) import qualified Simplex.Messaging.TMap as TM -import Simplex.Messaging.Transport (ATransport, THandleParams, TransportPeer (..)) +import Simplex.Messaging.Transport (ASrvTransport, THandleParams, TransportPeer (..)) import Simplex.Messaging.Transport.Server (AddHTTP, ServerCredentials, TransportServerConfig, loadFingerprint, loadServerCredential) import System.Exit (exitFailure) import System.Mem.Weak (Weak) import UnliftIO.STM data NtfServerConfig = NtfServerConfig - { transports :: [(ServiceName, ATransport, AddHTTP)], + { transports :: [(ServiceName, ASrvTransport, AddHTTP)], controlPort :: Maybe ServiceName, controlPortUserAuth :: Maybe BasicAuth, controlPortAdminAuth :: Maybe BasicAuth, diff --git a/src/Simplex/Messaging/Notifications/Server/Main.hs b/src/Simplex/Messaging/Notifications/Server/Main.hs index 23954506a..3709b1b2d 100644 --- a/src/Simplex/Messaging/Notifications/Server/Main.hs +++ b/src/Simplex/Messaging/Notifications/Server/Main.hs @@ -46,7 +46,7 @@ import Simplex.Messaging.Server.Main (strParse) import Simplex.Messaging.Server.Main.Init (iniDbOpts) import Simplex.Messaging.Server.QueueStore.Postgres.Config (PostgresStoreCfg (..)) import Simplex.Messaging.Server.StoreLog (closeStoreLog) -import Simplex.Messaging.Transport (ATransport, simplexMQVersion) +import Simplex.Messaging.Transport (ASrvTransport, simplexMQVersion) import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.Server (AddHTTP, ServerCredentials (..), TransportServerConfig (..), defaultTransportServerConfig) import Simplex.Messaging.Util (eitherToMaybe, ifM, tshow) @@ -286,7 +286,7 @@ ntfServerCLI cfgPath logPath = putStrLn "Configure notification server storage." exitFailure -printNtfServerConfig :: [(ServiceName, ATransport, AddHTTP)] -> PostgresStoreCfg -> IO () +printNtfServerConfig :: [(ServiceName, ASrvTransport, AddHTTP)] -> PostgresStoreCfg -> IO () printNtfServerConfig transports PostgresStoreCfg {dbOpts = DBOpts {connstr, schema}, dbStoreLogPath} = do B.putStrLn $ "PostgreSQL database: " <> connstr <> ", schema: " <> schema printServerConfig "NTF" transports dbStoreLogPath diff --git a/src/Simplex/Messaging/Notifications/Transport.hs b/src/Simplex/Messaging/Notifications/Transport.hs index a563a2689..87fcbeecc 100644 --- a/src/Simplex/Messaging/Notifications/Transport.hs +++ b/src/Simplex/Messaging/Notifications/Transport.hs @@ -110,7 +110,7 @@ instance Encoding NtfClientHandshake where pure NtfClientHandshake {ntfVersion, keyHash} -- | Notifcations server transport handshake. -ntfServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c 'TServer) +ntfServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c 'TServer -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c 'TServer) ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c let sk = C.signX509 serverSignKey $ C.publicToX509 k @@ -126,7 +126,7 @@ ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do Nothing -> throwE TEVersion -- | Notifcations server client transport handshake. -ntfClientHandshake :: forall c. Transport c => c -> C.KeyHash -> VersionRangeNTF -> Bool -> ExceptT TransportError IO (THandleNTF c 'TClient) +ntfClientHandshake :: forall c. Transport c => c 'TClient -> C.KeyHash -> VersionRangeNTF -> Bool -> ExceptT TransportError IO (THandleNTF c 'TClient) ntfClientHandshake c keyHash ntfVRange _proxyServer = do let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c NtfServerHandshake {sessionId = sessId, ntfVersionRange, authPubKey = sk'} <- getHandshake th @@ -137,7 +137,7 @@ ntfClientHandshake c keyHash ntfVRange _proxyServer = do ck_ <- forM sk' $ \signedKey -> liftEitherWith (const $ TEHandshake BAD_AUTH) $ do serverKey <- getServerVerifyKey c pubKey <- C.verifyX509 serverKey signedKey - (,(getServerCerts c, signedKey)) <$> (C.x509ToPublic (pubKey, []) >>= C.pubKey) + (,(getPeerCertChain c, signedKey)) <$> (C.x509ToPublic (pubKey, []) >>= C.pubKey) let v = maxVersion vr sendHandshake th $ NtfClientHandshake {ntfVersion = v, keyHash} pure $ ntfThHandleClient th v vr ck_ @@ -160,7 +160,7 @@ ntfThHandle_ th@THandle {params} v vr thAuth = params' = params {thVersion = v, thServerVRange = vr, thAuth, implySessId = v3, batch = v3} in (th :: THandleNTF c p) {params = params'} -ntfTHandle :: Transport c => c -> THandleNTF c p +ntfTHandle :: Transport c => c p -> THandleNTF c p ntfTHandle c = THandle {connection = c, params} where v = VersionNTF 0 diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index cb2eea43b..cde7f9bdf 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -305,7 +305,7 @@ data SParty :: Party -> Type where SRecipient :: SParty Recipient SSender :: SParty Sender SNotifier :: SParty Notifier - SSenderLink :: SParty LinkClient + SSenderLink :: SParty LinkClient SProxiedClient :: SParty ProxiedClient instance TestEquality SParty where @@ -1466,7 +1466,7 @@ transmissionP THandleParams {sessionId, implySessId} = do class (ProtocolTypeI (ProtoType msg), ProtocolEncoding v err msg, ProtocolEncoding v err (ProtoCommand msg), Show err, Show msg) => Protocol v err msg | msg -> v, msg -> err where type ProtoCommand msg = cmd | cmd -> msg type ProtoType msg = (sch :: ProtocolType) | sch -> msg - protocolClientHandshake :: forall c. Transport c => c -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRange v -> Bool -> ExceptT TransportError IO (THandle v c 'TClient) + protocolClientHandshake :: forall c. Transport c => c 'TClient -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRange v -> Bool -> ExceptT TransportError IO (THandle v c 'TClient) protocolPing :: ProtoCommand msg protocolError :: msg -> Maybe err diff --git a/src/Simplex/Messaging/Server.hs b/src/Simplex/Messaging/Server.hs index 5c36e7f6b..755548ad6 100644 --- a/src/Simplex/Messaging/Server.hs +++ b/src/Simplex/Messaging/Server.hs @@ -79,6 +79,7 @@ import Data.Time.Clock.System (SystemTime (..), getSystemTime) import Data.Time.Format.ISO8601 (iso8601Show) import Data.Type.Equality import Data.Typeable (cast) +import qualified Data.X509 as X import GHC.Conc.Signal import GHC.IORef (atomicSwapIORef) import GHC.Stats (getRTSStats) @@ -177,28 +178,28 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg, startOpt ) `finally` stopServer s where - runServer :: (ServiceName, ATransport, AddHTTP) -> M () + runServer :: (ServiceName, ASrvTransport, AddHTTP) -> M () runServer (tcpPort, ATransport t, addHTTP) = do - smpCreds <- asks tlsServerCreds + smpCreds@(srvCert, srvKey) <- asks tlsServerCreds httpCreds_ <- asks httpServerCreds ss <- liftIO newSocketState asks sockets >>= atomically . (`modifyTVar'` ((tcpPort, ss) :)) - serverSignKey <- either fail pure $ fromTLSCredentials smpCreds + srvSignKey <- either fail pure $ fromTLSPrivKey srvKey env <- ask liftIO $ case (httpCreds_, attachHTTP_) of (Just httpCreds, Just attachHTTP) | addHTTP -> runTransportServerState_ ss started tcpPort defaultSupportedParamsHTTPS chooseCreds (Just combinedALPNs) tCfg $ \s h -> case cast h of - Just TLS {tlsContext} | maybe False (`elem` httpALPN) (getSessionALPN h) -> labelMyThread "https client" >> attachHTTP s tlsContext - _ -> runClient serverSignKey t h `runReaderT` env + Just (TLS {tlsContext} :: TLS 'TServer) | maybe False (`elem` httpALPN) (getSessionALPN h) -> labelMyThread "https client" >> attachHTTP s tlsContext + _ -> runClient srvCert srvSignKey t h `runReaderT` env where chooseCreds = maybe smpCreds (\_host -> httpCreds) combinedALPNs = supportedSMPHandshakes <> httpALPN httpALPN :: [ALPN] httpALPN = ["h2", "http/1.1"] _ -> - runTransportServerState ss started tcpPort defaultSupportedParams smpCreds (Just supportedSMPHandshakes) tCfg $ \h -> runClient serverSignKey t h `runReaderT` env - fromTLSCredentials (_, pk) = C.x509ToPrivate (pk, []) >>= C.privKey + runTransportServerState ss started tcpPort defaultSupportedParams smpCreds (Just supportedSMPHandshakes) tCfg $ \h -> runClient srvCert srvSignKey t h `runReaderT` env + fromTLSPrivKey pk = C.x509ToPrivate (pk, []) >>= C.privKey sigIntHandlerThread :: M () sigIntHandlerThread = do @@ -589,13 +590,13 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg, startOpt subClientsCount <- IS.size <$> readTVarIO subClients pure RTSubscriberMetrics {subsCount, subClientsCount} - runClient :: Transport c => C.APrivateSignKey -> TProxy c -> c -> M () - runClient signKey tp h = do + runClient :: Transport c => X.CertificateChain -> C.APrivateSignKey -> TProxy c 'TServer -> c 'TServer -> M () + runClient srvCert srvSignKey tp h = do kh <- asks serverIdentity ks <- atomically . C.generateKeyPair =<< asks random ServerConfig {smpServerVRange, smpHandshakeTimeout} <- asks config labelMyThread $ "smp handshake for " <> transportName tp - liftIO (timeout smpHandshakeTimeout . runExceptT $ smpServerHandshake signKey h ks kh smpServerVRange) >>= \case + liftIO (timeout smpHandshakeTimeout . runExceptT $ smpServerHandshake srvCert srvSignKey h ks kh smpServerVRange) >>= \case Just (Right th) -> runClientTransport th _ -> pure () diff --git a/src/Simplex/Messaging/Server/CLI.hs b/src/Simplex/Messaging/Server/CLI.hs index ceb131b7f..275e24797 100644 --- a/src/Simplex/Messaging/Server/CLI.hs +++ b/src/Simplex/Messaging/Server/CLI.hs @@ -34,7 +34,7 @@ import Simplex.Messaging.Encoding.String import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), ProtocolServer (..), ProtocolTypeI) import Simplex.Messaging.Server.Env.STM (AServerStoreCfg (..), ServerStoreCfg (..), StartOptions (..), StorePaths (..)) import Simplex.Messaging.Server.QueueStore.Postgres.Config (PostgresStoreCfg (..)) -import Simplex.Messaging.Transport (ATransport (..), TLS, Transport (..)) +import Simplex.Messaging.Transport (ASrvTransport, ATransport (..), TLS, Transport (..)) import Simplex.Messaging.Transport.Server (AddHTTP, loadFileFingerprint) import Simplex.Messaging.Transport.WebSockets (WS) import Simplex.Messaging.Util (eitherToMaybe, whenM) @@ -363,7 +363,7 @@ checkSavedFingerprint cfgPath x509cfg = do where c = combine cfgPath . ($ x509cfg) -iniTransports :: Ini -> [(ServiceName, ATransport, AddHTTP)] +iniTransports :: Ini -> [(ServiceName, ASrvTransport, AddHTTP)] iniTransports ini = let smpPorts = ports $ strictIni "TRANSPORT" "port" ini ws = strictIni "TRANSPORT" "websockets" ini @@ -373,7 +373,7 @@ iniTransports ini = | otherwise = ports ws \\ smpPorts in ts (transport @TLS) smpPorts <> ts (transport @WS) wsPorts where - ts :: ATransport -> [ServiceName] -> [(ServiceName, ATransport, AddHTTP)] + ts :: ASrvTransport -> [ServiceName] -> [(ServiceName, ASrvTransport, AddHTTP)] ts t = map (\port -> (port, t, webPort == Just port)) webPort = T.unpack <$> eitherToMaybe (lookupValue "WEB" "https" ini) ports = map T.unpack . T.splitOn "," @@ -387,14 +387,14 @@ iniDBOptions ini _default@DBOpts {connstr, schema, poolSize} = createSchema = False } -printServerConfig :: String -> [(ServiceName, ATransport, AddHTTP)] -> Maybe FilePath -> IO () +printServerConfig :: String -> [(ServiceName, ASrvTransport, AddHTTP)] -> Maybe FilePath -> IO () printServerConfig protocol transports logFile = do putStrLn $ case logFile of Just f -> "Store log: " <> f _ -> "Store log disabled." printServerTransports protocol transports -printServerTransports :: String -> [(ServiceName, ATransport, AddHTTP)] -> IO () +printServerTransports :: String -> [(ServiceName, ASrvTransport, AddHTTP)] -> IO () printServerTransports protocol ts = do forM_ ts $ \(p, ATransport t, addHTTP) -> do let descr = p <> " (" <> transportName t <> ")..." @@ -405,7 +405,7 @@ printServerTransports protocol ts = do "\nWARNING: the clients will use port 443 by default soon.\n\ \Set `port` in smp-server.ini section [TRANSPORT] to `5223,443`\n" -printSMPServerConfig :: [(ServiceName, ATransport, AddHTTP)] -> AServerStoreCfg -> IO () +printSMPServerConfig :: [(ServiceName, ASrvTransport, AddHTTP)] -> AServerStoreCfg -> IO () printSMPServerConfig transports (ASSCfg _ _ cfg) = case cfg of SSCMemory sp_ -> printServerConfig "SMP" transports $ (\StorePaths {storeLogFile} -> storeLogFile) <$> sp_ SSCMemoryJournal {storeLogFile} -> printServerConfig "SMP" transports $ Just storeLogFile diff --git a/src/Simplex/Messaging/Server/Env/STM.hs b/src/Simplex/Messaging/Server/Env/STM.hs index 12d03c8f8..553d696fb 100644 --- a/src/Simplex/Messaging/Server/Env/STM.hs +++ b/src/Simplex/Messaging/Server/Env/STM.hs @@ -119,7 +119,7 @@ import Simplex.Messaging.Server.StoreLog import Simplex.Messaging.Server.StoreLog.ReadWrite import Simplex.Messaging.TMap (TMap) import qualified Simplex.Messaging.TMap as TM -import Simplex.Messaging.Transport (ATransport, VersionRangeSMP, VersionSMP) +import Simplex.Messaging.Transport (ASrvTransport, VersionRangeSMP, VersionSMP) import Simplex.Messaging.Transport.Server import Simplex.Messaging.Util (ifM, whenM, ($>>=)) import System.Directory (doesFileExist) @@ -129,7 +129,7 @@ import System.Mem.Weak (Weak) import UnliftIO.STM data ServerConfig = ServerConfig - { transports :: [(ServiceName, ATransport, AddHTTP)], + { transports :: [(ServiceName, ASrvTransport, AddHTTP)], smpHandshakeTimeout :: Int, tbqSize :: Natural, msgQueueQuota :: Int, diff --git a/src/Simplex/Messaging/Transport.hs b/src/Simplex/Messaging/Transport.hs index e815d6f36..13322446b 100644 --- a/src/Simplex/Messaging/Transport.hs +++ b/src/Simplex/Messaging/Transport.hs @@ -61,7 +61,10 @@ module Simplex.Messaging.Transport Transport (..), TProxy (..), ATransport (..), + ASrvTransport, TransportPeer (..), + STransportPeer (..), + TransportPeerI (..), getServerVerifyKey, -- * TLS Transport @@ -107,6 +110,7 @@ import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.Default (def) import Data.Functor (($>)) +import Data.Kind (Type) import Data.Tuple (swap) import Data.Typeable (Typeable) import Data.Version (showVersion) @@ -241,68 +245,75 @@ data TransportConfig = TransportConfig transportTimeout :: Maybe Int } -class Typeable c => Transport c where - transport :: ATransport - transport = ATransport (TProxy @c) +class Typeable c => Transport (c :: TransportPeer -> Type) where + transport :: forall p. ATransport p + transport = ATransport (TProxy @c @p) - transportName :: TProxy c -> String + transportName :: TProxy c p -> String - transportPeer :: c -> TransportPeer + transportConfig :: c p -> TransportConfig - transportConfig :: c -> TransportConfig + -- | Upgrade TLS context to connection + getTransportConnection :: TransportPeerI p => TransportConfig -> X.CertificateChain -> T.Context -> IO (c p) - -- | Upgrade server TLS context to connection (used in the server) - getServerConnection :: TransportConfig -> X.CertificateChain -> T.Context -> IO c - - -- | Upgrade client TLS context to connection (used in the client) - getClientConnection :: TransportConfig -> X.CertificateChain -> T.Context -> IO c - - getServerCerts :: c -> X.CertificateChain + -- | TLS certificate chain, server's in the client, client's in the server (empty chain) + getPeerCertChain :: c p -> X.CertificateChain -- | tls-unique channel binding per RFC5929 - tlsUnique :: c -> SessionId + tlsUnique :: c p -> SessionId -- | ALPN value negotiated for the session - getSessionALPN :: c -> Maybe ALPN + getSessionALPN :: c p -> Maybe ALPN -- | Close connection - closeConnection :: c -> IO () + closeConnection :: c p -> IO () -- | Read fixed number of bytes from connection - cGet :: c -> Int -> IO ByteString + cGet :: c p -> Int -> IO ByteString -- | Write bytes to connection - cPut :: c -> ByteString -> IO () + cPut :: c p -> ByteString -> IO () -- | Receive ByteString from connection, allowing LF or CRLF termination. - getLn :: c -> IO ByteString + getLn :: c p -> IO ByteString -- | Send ByteString to connection terminating it with CRLF. - putLn :: c -> ByteString -> IO () + putLn :: c p -> ByteString -> IO () putLn c = cPut c . (<> "\r\n") data TransportPeer = TClient | TServer deriving (Eq, Show) -data TProxy c = TProxy +data STransportPeer (p :: TransportPeer) where + STClient :: STransportPeer 'TClient + STServer :: STransportPeer 'TServer -data ATransport = forall c. Transport c => ATransport (TProxy c) +class TransportPeerI p where sTransportPeer :: STransportPeer p -getServerVerifyKey :: Transport c => c -> Either String C.APublicVerifyKey +instance TransportPeerI 'TClient where sTransportPeer = STClient + +instance TransportPeerI 'TServer where sTransportPeer = STServer + +data TProxy (c :: TransportPeer -> Type) (p :: TransportPeer) = TProxy + +data ATransport p = forall c. Transport c => ATransport (TProxy c p) + +type ASrvTransport = ATransport 'TServer + +getServerVerifyKey :: Transport c => c 'TClient -> Either String C.APublicVerifyKey getServerVerifyKey c = - case getServerCerts c of + case getPeerCertChain c of X.CertificateChain (server : _ca) -> C.x509ToPublic (X.certPubKey . X.signedObject $ X.getSigned server, []) >>= C.pubKey _ -> Left "no certificate chain" -- * TLS Transport -data TLS = TLS +data TLS (p :: TransportPeer) = TLS { tlsContext :: T.Context, - tlsPeer :: TransportPeer, tlsUniq :: ByteString, tlsBuffer :: TBuffer, tlsALPN :: Maybe ALPN, - tlsServerCerts :: X.CertificateChain, + tlsPeerCert :: X.CertificateChain, tlsTransportConfig :: TransportConfig } @@ -317,21 +328,22 @@ connectTLS host_ TransportConfig {logTLSErrors} params sock = logThrow e = putStrLn ("TLS error" <> host <> ": " <> show e) >> E.throwIO e host = maybe "" (\h -> " (" <> h <> ")") host_ -getTLS :: TransportPeer -> TransportConfig -> X.CertificateChain -> T.Context -> IO TLS -getTLS tlsPeer cfg tlsServerCerts cxt = withTlsUnique tlsPeer cxt newTLS +getTLS :: forall p. TransportPeerI p => TransportConfig -> X.CertificateChain -> T.Context -> IO (TLS p) +getTLS cfg tlsPeerCert cxt = withTlsUnique @TLS @p cxt newTLS where newTLS tlsUniq = do tlsBuffer <- newTBuffer tlsALPN <- T.getNegotiatedProtocol cxt - pure TLS {tlsContext = cxt, tlsALPN, tlsTransportConfig = cfg, tlsServerCerts, tlsPeer, tlsUniq, tlsBuffer} + pure TLS {tlsContext = cxt, tlsALPN, tlsTransportConfig = cfg, tlsPeerCert, tlsUniq, tlsBuffer} -withTlsUnique :: TransportPeer -> T.Context -> (ByteString -> IO c) -> IO c -withTlsUnique peer cxt f = - cxtFinished peer cxt +withTlsUnique :: forall c p. TransportPeerI p => T.Context -> (ByteString -> IO (c p)) -> IO (c p) +withTlsUnique cxt f = + cxtFinished cxt >>= maybe (closeTLS cxt >> ioe_EOF) f where - cxtFinished TServer = T.getPeerFinished - cxtFinished TClient = T.getFinished + cxtFinished = case sTransportPeer @p of + STServer -> T.getPeerFinished + STClient -> T.getFinished closeTLS :: T.Context -> IO () closeTLS ctx = @@ -375,26 +387,31 @@ defaultSupportedParamsHTTPS = instance Transport TLS where transportName _ = "TLS" - transportPeer = tlsPeer + {-# INLINE transportName #-} transportConfig = tlsTransportConfig - getServerConnection = getTLS TServer - getClientConnection = getTLS TClient - getServerCerts = tlsServerCerts + {-# INLINE transportConfig #-} + getTransportConnection = getTLS + {-# INLINE getTransportConnection #-} + getPeerCertChain = tlsPeerCert + {-# INLINE getPeerCertChain #-} getSessionALPN = tlsALPN + {-# INLINE getSessionALPN #-} tlsUnique = tlsUniq + {-# INLINE tlsUnique #-} closeConnection tls = closeTLS $ tlsContext tls + {-# INLINE closeConnection #-} -- https://hackage.haskell.org/package/tls-1.6.0/docs/Network-TLS.html#v:recvData -- this function may return less than requested number of bytes - cGet :: TLS -> Int -> IO ByteString + cGet :: TLS p -> Int -> IO ByteString cGet TLS {tlsContext, tlsBuffer, tlsTransportConfig = TransportConfig {transportTimeout = t_}} n = getBuffered tlsBuffer n t_ (T.recvData tlsContext) - cPut :: TLS -> ByteString -> IO () + cPut :: TLS p -> ByteString -> IO () cPut TLS {tlsContext, tlsTransportConfig = TransportConfig {transportTimeout = t_}} = withTimedErr t_ . T.sendData tlsContext . LB.fromStrict - getLn :: TLS -> IO ByteString + getLn :: TLS p -> IO ByteString getLn TLS {tlsContext, tlsBuffer} = do getLnBuffered tlsBuffer (T.recvData tlsContext) `E.catches` [E.Handler handleTlsEOF, E.Handler handleEOF] where @@ -407,7 +424,7 @@ instance Transport TLS where -- | The handle for SMP encrypted transport connection over Transport. data THandle v c p = THandle - { connection :: c, + { connection :: c p, params :: THandleParams v p } @@ -587,13 +604,12 @@ tGetBlock THandle {connection = c, params = THandleParams {blockSize, encryptBlo -- | Server SMP transport handshake. -- -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a -smpServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c 'TServer) -smpServerHandshake serverSignKey c (k, pk) kh smpVRange = do +smpServerHandshake :: forall c. Transport c => X.CertificateChain -> C.APrivateSignKey -> c 'TServer -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c 'TServer) +smpServerHandshake srvCert srvSignKey c (k, pk) kh smpVRange = do let th@THandle {params = THandleParams {sessionId}} = smpTHandle c - sk = C.signX509 serverSignKey $ C.publicToX509 k - certChain = getServerCerts c + sk = C.signX509 srvSignKey $ C.publicToX509 k smpVersionRange = maybe legacyServerSMPRelayVRange (const smpVRange) $ getSessionALPN c - sendHandshake th $ ServerHandshake {sessionId, smpVersionRange, authPubKey = Just (certChain, sk)} + sendHandshake th $ ServerHandshake {sessionId, smpVersionRange, authPubKey = Just (srvCert, sk)} getHandshake th >>= \case ClientHandshake {smpVersion = v, keyHash, authPubKey = k', proxyServer} | keyHash /= kh -> @@ -606,7 +622,7 @@ smpServerHandshake serverSignKey c (k, pk) kh smpVRange = do -- | Client SMP transport handshake. -- -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a -smpClientHandshake :: forall c. Transport c => c -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> Bool -> ExceptT TransportError IO (THandleSMP c 'TClient) +smpClientHandshake :: forall c. Transport c => c 'TClient -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> Bool -> ExceptT TransportError IO (THandleSMP c 'TClient) smpClientHandshake c ks_ keyHash@(C.KeyHash kh) vRange proxyServer = do let th@THandle {params = THandleParams {sessionId}} = smpTHandle c ServerHandshake {sessionId = sessId, smpVersionRange, authPubKey} <- getHandshake th @@ -689,7 +705,7 @@ sendHandshake th = ExceptT . tPutBlock th . smpEncode getHandshake :: (Transport c, Encoding smp) => THandle v c p -> ExceptT TransportError IO smp getHandshake th = ExceptT $ (first (\_ -> TEHandshake PARSE) . A.parseOnly smpP =<<) <$> tGetBlock th -smpTHandle :: Transport c => c -> THandleSMP c p +smpTHandle :: Transport c => c p -> THandleSMP c p smpTHandle c = THandle {connection = c, params} where v = VersionSMP 0 diff --git a/src/Simplex/Messaging/Transport/Client.hs b/src/Simplex/Messaging/Transport/Client.hs index 074a85dad..6fc36d143 100644 --- a/src/Simplex/Messaging/Transport/Client.hs +++ b/src/Simplex/Messaging/Transport/Client.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} @@ -142,10 +143,10 @@ clientTransportConfig TransportClientConfig {logTLSErrors} = TransportConfig {logTLSErrors, transportTimeout = Nothing} -- | Connect to passed TCP host:port and pass handle to the client. -runTransportClient :: Transport c => TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a +runTransportClient :: Transport c => TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c 'TClient -> IO a) -> IO a runTransportClient = runTLSTransportClient defaultSupportedParams Nothing -runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a +runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c 'TClient -> IO a) -> IO a runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpKeepAlive, clientCredentials, alpn, useSNI} socksCreds host port keyHash client = do serverCert <- newEmptyTMVarIO let hostName = B.unpack $ strEncode host @@ -165,7 +166,7 @@ runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, logError "onServerCertificate didn't fire or failed to get cert chain" closeTLS tls >> error "onServerCertificate failed" Just c -> pure c - getClientConnection tCfg chain tls + getTransportConnection tCfg chain tls client c `E.finally` closeConnection c where hostAddr = \case diff --git a/src/Simplex/Messaging/Transport/HTTP2.hs b/src/Simplex/Messaging/Transport/HTTP2.hs index 10522c5bc..ae0469c91 100644 --- a/src/Simplex/Messaging/Transport/HTTP2.hs +++ b/src/Simplex/Messaging/Transport/HTTP2.hs @@ -22,10 +22,10 @@ import qualified System.TimeManager as TI defaultHTTP2BufferSize :: BufferSize defaultHTTP2BufferSize = 32768 -withHTTP2 :: BufferSize -> (Config -> IO a) -> IO () -> TLS -> IO a +withHTTP2 :: BufferSize -> (Config -> IO a) -> IO () -> TLS p -> IO a withHTTP2 sz run fin c = E.bracket (allocHTTP2Config c sz) (\cfg -> freeSimpleConfig cfg `E.finally` fin) run -allocHTTP2Config :: TLS -> BufferSize -> IO Config +allocHTTP2Config :: TLS p -> BufferSize -> IO Config allocHTTP2Config c sz = do buf <- mallocBytes sz tm <- TI.initialize $ 30 * 1000000 diff --git a/src/Simplex/Messaging/Transport/HTTP2/Client.hs b/src/Simplex/Messaging/Transport/HTTP2/Client.hs index eda71cb98..4be91a00a 100644 --- a/src/Simplex/Messaging/Transport/HTTP2/Client.hs +++ b/src/Simplex/Messaging/Transport/HTTP2/Client.hs @@ -1,9 +1,12 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} module Simplex.Messaging.Transport.HTTP2.Client where @@ -24,7 +27,7 @@ import qualified Network.TLS as T import Numeric.Natural (Natural) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Transport (ALPN, SessionId, TLS (tlsALPN), getServerCerts, getServerVerifyKey, tlsUniq) +import Simplex.Messaging.Transport (ALPN, STransportPeer (..), SessionId, TLS (tlsALPN, tlsPeerCert, tlsUniq), TransportPeer (..), TransportPeerI (..), getServerVerifyKey) import Simplex.Messaging.Transport.Client (TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTLSTransportClient) import Simplex.Messaging.Transport.HTTP2 import Simplex.Messaging.Util (eitherToMaybe) @@ -97,13 +100,14 @@ getVerifiedHTTP2Client socksCreds host port keyHash caStore config disconnected where setup = runHTTP2Client (suportedTLSParams config) caStore (transportConfig config) (bufferSize config) socksCreds host port keyHash -attachHTTP2Client :: HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> Int -> TLS -> IO (Either HTTP2ClientError HTTP2Client) +-- HTTP2 client can be run on both client and server TLS connections. +attachHTTP2Client :: forall p. TransportPeerI p => HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> Int -> TLS p -> IO (Either HTTP2ClientError HTTP2Client) attachHTTP2Client config host port disconnected bufferSize tls = getVerifiedHTTP2ClientWith config host port disconnected setup where - setup :: (TLS -> H.Client HTTP2Response) -> IO HTTP2Response + setup :: (TLS p -> H.Client HTTP2Response) -> IO HTTP2Response setup = runHTTP2ClientWith bufferSize host ($ tls) -getVerifiedHTTP2ClientWith :: HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> ((TLS -> H.Client HTTP2Response) -> IO HTTP2Response) -> IO (Either HTTP2ClientError HTTP2Client) +getVerifiedHTTP2ClientWith :: forall p. TransportPeerI p => HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> ((TLS p -> H.Client HTTP2Response) -> IO HTTP2Response) -> IO (Either HTTP2ClientError HTTP2Client) getVerifiedHTTP2ClientWith config host port disconnected setup = (mkHTTPS2Client >>= runClient) `E.catch` \(e :: IOException) -> pure . Left $ HCIOError e @@ -124,15 +128,17 @@ getVerifiedHTTP2ClientWith config host port disconnected setup = Just (Left e) -> pure $ Left e Nothing -> cancel action $> Left HCNetworkError - client :: HClient -> TMVar (Either HTTP2ClientError HTTP2Client) -> TLS -> H.Client HTTP2Response + client :: HClient -> TMVar (Either HTTP2ClientError HTTP2Client) -> TLS p -> H.Client HTTP2Response client c cVar tls sendReq = do sessionTs <- getCurrentTime let c' = HTTP2Client { action = Nothing, client_ = c, - serverKey = eitherToMaybe $ getServerVerifyKey tls, - serverCerts = getServerCerts tls, + serverKey = case sTransportPeer @p of + STClient -> eitherToMaybe $ getServerVerifyKey tls + STServer -> Nothing, + serverCerts = tlsPeerCert tls, sendReq, sessionTs, sessionId = tlsUniq tls, @@ -179,14 +185,15 @@ sendRequestDirect HTTP2Client {client_ = HClient {config, disconnected}, sendReq http2RequestTimeout :: HTTP2ClientConfig -> Maybe Int -> Int http2RequestTimeout HTTP2ClientConfig {connTimeout} = maybe connTimeout (connTimeout +) -runHTTP2Client :: forall a. T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> BufferSize -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (TLS -> H.Client a) -> IO a +runHTTP2Client :: forall a. T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> BufferSize -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (TLS 'TClient -> H.Client a) -> IO a runHTTP2Client tlsParams caStore tcConfig bufferSize socksCreds host port keyHash = runHTTP2ClientWith bufferSize host setup where - setup :: (TLS -> IO a) -> IO a + setup :: (TLS 'TClient -> IO a) -> IO a setup = runTLSTransportClient tlsParams caStore tcConfig socksCreds host port keyHash -runHTTP2ClientWith :: forall a. BufferSize -> TransportHost -> ((TLS -> IO a) -> IO a) -> (TLS -> H.Client a) -> IO a +-- HTTP2 client can be run on both client and server TLS connections. +runHTTP2ClientWith :: forall a p. BufferSize -> TransportHost -> ((TLS p -> IO a) -> IO a) -> (TLS p -> H.Client a) -> IO a runHTTP2ClientWith bufferSize host setup client = setup $ \tls -> withHTTP2 bufferSize (run tls) (pure ()) tls where - run :: TLS -> H.Config -> IO a + run :: TLS p -> H.Config -> IO a run tls cfg = H.run (ClientConfig "https" (strEncode host) 20) cfg $ client tls diff --git a/src/Simplex/Messaging/Transport/HTTP2/Server.hs b/src/Simplex/Messaging/Transport/HTTP2/Server.hs index 7dbed97ef..12234b775 100644 --- a/src/Simplex/Messaging/Transport/HTTP2/Server.hs +++ b/src/Simplex/Messaging/Transport/HTTP2/Server.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE NamedFieldPuns #-} module Simplex.Messaging.Transport.HTTP2.Server where @@ -67,10 +68,11 @@ runHTTP2Server started port bufferSize srvSupported srvCreds alpn_ transportConf where setup = runTransportServer started port srvSupported srvCreds alpn_ transportConfig -runHTTP2ServerWith :: BufferSize -> ((TLS -> IO ()) -> a) -> HTTP2ServerFunc -> a +-- HTTP2 server can be run on both client and server TLS connections. +runHTTP2ServerWith :: BufferSize -> ((TLS p -> IO ()) -> a) -> HTTP2ServerFunc -> a runHTTP2ServerWith = runHTTP2ServerWith_ Nothing (\_sessId -> pure ()) -runHTTP2ServerWith_ :: Maybe ExpirationConfig -> (SessionId -> IO ()) -> BufferSize -> ((TLS -> IO ()) -> a) -> HTTP2ServerFunc -> a +runHTTP2ServerWith_ :: Maybe ExpirationConfig -> (SessionId -> IO ()) -> BufferSize -> ((TLS p -> IO ()) -> a) -> HTTP2ServerFunc -> a runHTTP2ServerWith_ expCfg_ clientFinished bufferSize setup http2Server = setup $ \tls -> do activeAt <- newTVarIO =<< getSystemTime tid_ <- mapM (forkIO . expireInactiveClient tls activeAt) expCfg_ diff --git a/src/Simplex/Messaging/Transport/Server.hs b/src/Simplex/Messaging/Transport/Server.hs index 0be54eb7b..cdcacb795 100644 --- a/src/Simplex/Messaging/Transport/Server.hs +++ b/src/Simplex/Messaging/Transport/Server.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -87,31 +88,31 @@ serverTransportConfig TransportServerConfig {logTLSErrors} = -- | Run transport server (plain TCP or WebSockets) on passed TCP port and signal when server started and stopped via passed TMVar. -- -- All accepted connections are passed to the passed function. -runTransportServer :: forall c. Transport c => TMVar Bool -> ServiceName -> T.Supported -> T.Credential -> Maybe [ALPN] -> TransportServerConfig -> (c -> IO ()) -> IO () +runTransportServer :: forall c. Transport c => TMVar Bool -> ServiceName -> T.Supported -> T.Credential -> Maybe [ALPN] -> TransportServerConfig -> (c 'TServer -> IO ()) -> IO () runTransportServer started port srvSupported srvCreds alpn_ cfg server = do ss <- newSocketState runTransportServerState ss started port srvSupported srvCreds alpn_ cfg server -runTransportServerState :: forall c . Transport c => SocketState -> TMVar Bool -> ServiceName -> T.Supported -> T.Credential -> Maybe [ALPN] -> TransportServerConfig -> (c -> IO ()) -> IO () +runTransportServerState :: forall c . Transport c => SocketState -> TMVar Bool -> ServiceName -> T.Supported -> T.Credential -> Maybe [ALPN] -> TransportServerConfig -> (c 'TServer -> IO ()) -> IO () runTransportServerState ss started port srvSupported srvCreds alpn_ cfg server = runTransportServerState_ ss started port srvSupported (const srvCreds) alpn_ cfg (const server) -runTransportServerState_ :: forall c . Transport c => SocketState -> TMVar Bool -> ServiceName -> T.Supported -> (Maybe HostName -> T.Credential) -> Maybe [ALPN] -> TransportServerConfig -> (Socket -> c -> IO ()) -> IO () -runTransportServerState_ ss started port = runTransportServerSocketState ss started (startTCPServer started Nothing port) (transportName (TProxy :: TProxy c)) +runTransportServerState_ :: forall c . Transport c => SocketState -> TMVar Bool -> ServiceName -> T.Supported -> (Maybe HostName -> T.Credential) -> Maybe [ALPN] -> TransportServerConfig -> (Socket -> c 'TServer -> IO ()) -> IO () +runTransportServerState_ ss started port = runTransportServerSocketState ss started (startTCPServer started Nothing port) (transportName (TProxy :: TProxy c 'TServer)) -- | Run a transport server with provided connection setup and handler. -runTransportServerSocket :: Transport a => TMVar Bool -> IO Socket -> String -> T.Credential -> T.ServerParams -> TransportServerConfig -> (a -> IO ()) -> IO () +runTransportServerSocket :: Transport c => TMVar Bool -> IO Socket -> String -> T.Credential -> T.ServerParams -> TransportServerConfig -> (c 'TServer -> IO ()) -> IO () runTransportServerSocket started getSocket threadLabel srvCreds srvParams cfg server = do ss <- newSocketState runTransportServerSocketState_ ss started getSocket threadLabel (const srvCreds) srvParams cfg (const server) -runTransportServerSocketState :: Transport a => SocketState -> TMVar Bool -> IO Socket -> String -> T.Supported -> (Maybe HostName -> T.Credential) -> Maybe [ALPN] -> TransportServerConfig -> (Socket -> a -> IO ()) -> IO () +runTransportServerSocketState :: Transport c => SocketState -> TMVar Bool -> IO Socket -> String -> T.Supported -> (Maybe HostName -> T.Credential) -> Maybe [ALPN] -> TransportServerConfig -> (Socket -> c 'TServer -> IO ()) -> IO () runTransportServerSocketState ss started getSocket threadLabel srvSupported srvCreds alpn_ = runTransportServerSocketState_ ss started getSocket threadLabel srvCreds srvParams where srvParams = supportedTLSServerParams_ srvSupported srvCreds alpn_ -- | Run a transport server with provided connection setup and handler. -runTransportServerSocketState_ :: Transport a => SocketState -> TMVar Bool -> IO Socket -> String -> (Maybe HostName -> (X.CertificateChain, X.PrivKey)) -> T.ServerParams -> TransportServerConfig -> (Socket -> a -> IO ()) -> IO () +runTransportServerSocketState_ :: Transport c => SocketState -> TMVar Bool -> IO Socket -> String -> (Maybe HostName -> (X.CertificateChain, X.PrivKey)) -> T.ServerParams -> TransportServerConfig -> (Socket -> c 'TServer -> IO ()) -> IO () runTransportServerSocketState_ ss started getSocket threadLabel srvCreds srvParams cfg server = do labelMyThread $ "transport server for " <> threadLabel runTCPServerSocket ss started getSocket $ \conn -> @@ -121,7 +122,7 @@ runTransportServerSocketState_ ss started getSocket threadLabel srvCreds srvPara setup conn = timeout (tlsSetupTimeout cfg) $ do labelMyThread $ threadLabel <> "/setup" tls <- connectTLS Nothing tCfg srvParams conn - getServerConnection tCfg (fst $ srvCreds Nothing) tls + getTransportConnection tCfg (fst $ srvCreds Nothing) tls -- | Run TCP server without TLS runLocalTCPServer :: TMVar Bool -> ServiceName -> (Socket -> IO ()) -> IO () diff --git a/src/Simplex/Messaging/Transport/WebSockets.hs b/src/Simplex/Messaging/Transport/WebSockets.hs index 866d0d197..34c27bedd 100644 --- a/src/Simplex/Messaging/Transport/WebSockets.hs +++ b/src/Simplex/Messaging/Transport/WebSockets.hs @@ -1,6 +1,11 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} module Simplex.Messaging.Transport.WebSockets (WS (..)) where @@ -15,11 +20,12 @@ import Network.WebSockets.Stream (Stream) import qualified Network.WebSockets.Stream as S import Simplex.Messaging.Transport ( ALPN, - TProxy, Transport (..), TransportConfig (..), TransportError (..), TransportPeer (..), + STransportPeer (..), + TransportPeerI (..), closeTLS, smpBlockSize, withTlsUnique, @@ -27,14 +33,13 @@ import Simplex.Messaging.Transport import Simplex.Messaging.Transport.Buffer (trimCR) import System.IO.Error (isEOFError) -data WS = WS - { wsPeer :: TransportPeer, - tlsUniq :: ByteString, +data WS (p :: TransportPeer) = WS + { tlsUniq :: ByteString, wsALPN :: Maybe ALPN, wsStream :: Stream, wsConnection :: Connection, wsTransportConfig :: TransportConfig, - wsServerCerts :: X.CertificateChain + wsPeerCert :: X.CertificateChain } websocketsOpts :: ConnectionOptions @@ -46,61 +51,50 @@ websocketsOpts = } instance Transport WS where - transportName :: TProxy WS -> String transportName _ = "WebSockets" - - transportPeer :: WS -> TransportPeer - transportPeer = wsPeer - - transportConfig :: WS -> TransportConfig + {-# INLINE transportName #-} transportConfig = wsTransportConfig - - getServerConnection :: TransportConfig -> X.CertificateChain -> T.Context -> IO WS - getServerConnection = getWS TServer - - getClientConnection :: TransportConfig -> X.CertificateChain -> T.Context -> IO WS - getClientConnection = getWS TClient - - getServerCerts :: WS -> X.CertificateChain - getServerCerts = wsServerCerts - - getSessionALPN :: WS -> Maybe ALPN + {-# INLINE transportConfig #-} + getTransportConnection = getWS + {-# INLINE getTransportConnection #-} + getPeerCertChain = wsPeerCert + {-# INLINE getPeerCertChain #-} getSessionALPN = wsALPN - - tlsUnique :: WS -> ByteString + {-# INLINE getSessionALPN #-} tlsUnique = tlsUniq - - closeConnection :: WS -> IO () + {-# INLINE tlsUnique #-} closeConnection = S.close . wsStream + {-# INLINE closeConnection #-} - cGet :: WS -> Int -> IO ByteString + cGet :: WS p -> Int -> IO ByteString cGet c n = do s <- receiveData (wsConnection c) if B.length s == n then pure s else E.throwIO TEBadBlock - cPut :: WS -> ByteString -> IO () + cPut :: WS p -> ByteString -> IO () cPut = sendBinaryData . wsConnection - getLn :: WS -> IO ByteString + getLn :: WS p -> IO ByteString getLn c = do s <- trimCR <$> receiveData (wsConnection c) if B.null s || B.last s /= '\n' then E.throwIO TEBadBlock else pure $ B.init s -getWS :: TransportPeer -> TransportConfig -> X.CertificateChain -> T.Context -> IO WS -getWS wsPeer cfg wsServerCerts cxt = withTlsUnique wsPeer cxt connectWS +getWS :: forall p. TransportPeerI p => TransportConfig -> X.CertificateChain -> T.Context -> IO (WS p) +getWS cfg wsPeerCert cxt = withTlsUnique @WS @p cxt connectWS where connectWS tlsUniq = do s <- makeTLSContextStream cxt - wsConnection <- connectPeer wsPeer s + wsConnection <- connectPeer s wsALPN <- T.getNegotiatedProtocol cxt - pure $ WS {wsPeer, tlsUniq, wsALPN, wsStream = s, wsConnection, wsTransportConfig = cfg, wsServerCerts} - connectPeer :: TransportPeer -> Stream -> IO Connection - connectPeer TServer = acceptClientRequest - connectPeer TClient = sendClientRequest + pure $ WS {tlsUniq, wsALPN, wsStream = s, wsConnection, wsTransportConfig = cfg, wsPeerCert} + connectPeer :: Stream -> IO Connection + connectPeer = case sTransportPeer @p of + STServer -> acceptClientRequest + STClient -> sendClientRequest acceptClientRequest s = makePendingConnectionFromStream s websocketsOpts >>= acceptRequest sendClientRequest s = newClientConnection s "" "/" websocketsOpts [] diff --git a/src/Simplex/RemoteControl/Client.hs b/src/Simplex/RemoteControl/Client.hs index acb86602c..09cf065f7 100644 --- a/src/Simplex/RemoteControl/Client.hs +++ b/src/Simplex/RemoteControl/Client.hs @@ -62,7 +62,7 @@ import Simplex.Messaging.Crypto.SNTRUP761 import Simplex.Messaging.Crypto.SNTRUP761.Bindings import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String (StrEncoding (..)) -import Simplex.Messaging.Transport (TSbChainKeys (..), TLS (..), cGet, cPut) +import Simplex.Messaging.Transport (TSbChainKeys (..), TLS (..), TransportPeer (..), cGet, cPut) import Simplex.Messaging.Transport.Buffer (peekBuffered) import Simplex.Messaging.Transport.Client (TransportClientConfig (..), TransportHost (..), defaultTransportClientConfig, runTransportClient) import Simplex.Messaging.Transport.Credentials (genCredentials, tlsCredentials) @@ -101,7 +101,7 @@ data RCHClient_ = RCHClient_ endSession :: TMVar () } -type RCHostConnection = (NonEmpty RCCtrlAddress, RCSignedInvitation, RCHostClient, RCStepTMVar (SessionCode, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing))) +type RCHostConnection = (NonEmpty RCCtrlAddress, RCSignedInvitation, RCHostClient, RCStepTMVar (SessionCode, TLS 'TServer, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing))) connectRCHost :: TVar ChaChaDRG -> RCHostPairing -> J.Value -> Bool -> Maybe RCCtrlAddress -> Maybe Word16 -> ExceptT RCErrorType IO RCHostConnection connectRCHost drg pairing@RCHostPairing {caKey, caCert, idPrivKey, knownHost} ctrlAppInfo multicast rcAddrPrefs_ port_ = do @@ -131,7 +131,7 @@ connectRCHost drg pairing@RCHostPairing {caKey, caCert, idPrivKey, knownHost} ct endSession <- newEmptyTMVarIO hostCAHash <- newEmptyTMVarIO pure RCHClient_ {startedPort, announcer, hostCAHash, endSession} - runClient :: RCHClient_ -> RCStepTMVar (SessionCode, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> RCHostKeys -> IO (Async ()) + runClient :: RCHClient_ -> RCStepTMVar (SessionCode, TLS 'TServer, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> RCHostKeys -> IO (Async ()) runClient RCHClient_ {startedPort, announcer, hostCAHash, endSession} r hostKeys = do tlsCreds <- genTLSCredentials drg caKey caCert startTLSServer port_ startedPort tlsCreds (tlsHooks r knownHost hostCAHash) $ \tls -> @@ -249,7 +249,7 @@ data RCCClient_ = RCCClient_ endSession :: TMVar () } -type RCCtrlConnection = (RCCtrlClient, RCStepTMVar (SessionCode, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing))) +type RCCtrlConnection = (RCCtrlClient, RCStepTMVar (SessionCode, TLS 'TClient, RCStepTMVar (RCCtrlSession, RCCtrlPairing))) -- app should determine whether it is a new or known pairing based on CA fingerprint in the invitation connectRCCtrl :: TVar ChaChaDRG -> RCVerifiedInvitation -> Maybe RCCtrlPairing -> J.Value -> ExceptT RCErrorType IO RCCtrlConnection @@ -280,7 +280,7 @@ connectRCCtrl_ drg pairing'@RCCtrlPairing {caKey, caCert} inv@RCInvitation {ca, confirmSession <- newEmptyTMVarIO endSession <- newEmptyTMVarIO pure RCCClient_ {confirmSession, endSession} - runClient :: RCCClient_ -> RCStepTMVar (SessionCode, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> ExceptT RCErrorType IO () + runClient :: RCCClient_ -> RCStepTMVar (SessionCode, TLS 'TClient, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> ExceptT RCErrorType IO () runClient RCCClient_ {confirmSession, endSession} r = do clientCredentials <- liftIO $ Just <$> genTLSCredentials drg caKey caCert let clientConfig = defaultTransportClientConfig {clientCredentials} @@ -315,12 +315,12 @@ catchRCError = catchAllErrors $ \e -> case fromException e of putRCError :: ExceptT RCErrorType IO a -> TMVar (Either RCErrorType b) -> ExceptT RCErrorType IO a a `putRCError` r = a `catchRCError` \e -> atomically (tryPutTMVar r $ Left e) >> throwE e -sendRCPacket :: Encoding a => TLS -> a -> ExceptT RCErrorType IO () +sendRCPacket :: Encoding a => TLS p -> a -> ExceptT RCErrorType IO () sendRCPacket tls pkt = do b <- liftEitherWith (const RCEBlockSize) $ C.pad (smpEncode pkt) xrcpBlockSize liftIO $ cPut tls b -receiveRCPacket :: Encoding a => TLS -> ExceptT RCErrorType IO a +receiveRCPacket :: Encoding a => TLS p -> ExceptT RCErrorType IO a receiveRCPacket tls = do b <- liftIO $ cGet tls xrcpBlockSize when (B.length b /= xrcpBlockSize) $ throwE RCEBlockSize diff --git a/src/Simplex/RemoteControl/Discovery.hs b/src/Simplex/RemoteControl/Discovery.hs index d0a326bba..108d79170 100644 --- a/src/Simplex/RemoteControl/Discovery.hs +++ b/src/Simplex/RemoteControl/Discovery.hs @@ -23,7 +23,7 @@ import Network.Info (IPv4 (..), NetworkInterface (..), getNetworkInterfaces) import qualified Network.Socket as N import qualified Network.TLS as TLS import qualified Network.UDP as UDP -import Simplex.Messaging.Transport (defaultSupportedParams) +import Simplex.Messaging.Transport (TransportPeer (..), defaultSupportedParams) import qualified Simplex.Messaging.Transport as Transport import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.Server (defaultTransportServerConfig, runTransportServerSocket, startTCPServer) @@ -68,7 +68,7 @@ preferAddress RCCtrlAddress {address, interface} addrs = matchAddr RCCtrlAddress {address = a} = a == address matchIface RCCtrlAddress {interface = i} = i == interface -startTLSServer :: Maybe Word16 -> TMVar (Maybe N.PortNumber) -> TLS.Credential -> TLS.ServerHooks -> (Transport.TLS -> IO ()) -> IO (Async ()) +startTLSServer :: Maybe Word16 -> TMVar (Maybe N.PortNumber) -> TLS.Credential -> TLS.ServerHooks -> (Transport.TLS 'TServer -> IO ()) -> IO (Async ()) startTLSServer port_ startedOnPort credentials hooks server = async . liftIO $ do started <- newEmptyTMVarIO bracketOnError (startTCPServer started Nothing $ maybe "0" show port_) (\_e -> setPort Nothing) $ \socket -> diff --git a/src/Simplex/RemoteControl/Types.hs b/src/Simplex/RemoteControl/Types.hs index bc191824a..76a643925 100644 --- a/src/Simplex/RemoteControl/Types.hs +++ b/src/Simplex/RemoteControl/Types.hs @@ -18,12 +18,13 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Data.Word (Word16) +import qualified Data.X509 as X import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.SNTRUP761.Bindings import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) -import Simplex.Messaging.Transport (TLS, TSbChainKeys) +import Simplex.Messaging.Transport (TLS, TSbChainKeys, TransportPeer (..)) import Simplex.Messaging.Transport.Client (TransportHost) import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version (VersionRange, VersionScope, mkVersionRange) @@ -140,7 +141,7 @@ $(JQ.deriveJSON defaultJSON {J.nullaryToObject = True} ''RCCtrlHello) -- | Long-term part of controller (desktop) connection to host (mobile) data RCHostPairing = RCHostPairing { caKey :: C.APrivateSignKey, - caCert :: C.SignedCertificate, + caCert :: X.SignedCertificate, idPrivKey :: C.PrivateKeyEd25519, knownHost :: Maybe KnownHostPairing } @@ -159,7 +160,7 @@ data RCCtrlAddress = RCCtrlAddress -- | Long-term part of host (mobile) connection to controller (desktop) data RCCtrlPairing = RCCtrlPairing { caKey :: C.APrivateSignKey, - caCert :: C.SignedCertificate, + caCert :: X.SignedCertificate, ctrlFingerprint :: C.KeyHash, -- long-term identity of connected remote controller idPubKey :: C.PublicKeyEd25519, dhPrivKey :: C.PrivateKeyX25519, @@ -173,7 +174,7 @@ data RCHostKeys = RCHostKeys -- Connected session with Host data RCHostSession = RCHostSession - { tls :: TLS, + { tls :: TLS 'TServer, sessionKeys :: HostSessKeys } @@ -186,7 +187,7 @@ data HostSessKeys = HostSessKeys -- Host: RCCtrlPairing + RCInvitation => (RCCtrlSession, RCCtrlPairing) data RCCtrlSession = RCCtrlSession - { tls :: TLS, + { tls :: TLS 'TClient, sessionKeys :: CtrlSessKeys } diff --git a/tests/AgentTests.hs b/tests/AgentTests.hs index eed1580ae..368e7c0e2 100644 --- a/tests/AgentTests.hs +++ b/tests/AgentTests.hs @@ -15,7 +15,7 @@ import AgentTests.MigrationTests (migrationTests) import AgentTests.ServerChoice (serverChoiceTests) import AgentTests.ShortLinkTests (shortLinkTests) import Simplex.Messaging.Server.Env.STM (AStoreType (..)) -import Simplex.Messaging.Transport (ATransport (..)) +import Simplex.Messaging.Transport (ASrvTransport) import Test.Hspec hiding (fit, it) #if defined(dbPostgres) @@ -38,7 +38,7 @@ agentCoreTests = do describe "Double ratchet tests" doubleRatchetTests describe "Short link tests" shortLinkTests -agentTests :: (ATransport, AStoreType) -> Spec +agentTests :: (ASrvTransport, AStoreType) -> Spec agentTests ps = do #if defined(dbPostgres) after_ (dropAllSchemasExceptSystem testDBConnectInfo) $ do diff --git a/tests/AgentTests/FunctionalAPITests.hs b/tests/AgentTests/FunctionalAPITests.hs index 12a289a0c..8c5bfec3f 100644 --- a/tests/AgentTests/FunctionalAPITests.hs +++ b/tests/AgentTests/FunctionalAPITests.hs @@ -102,7 +102,7 @@ import Simplex.Messaging.Server.Env.STM (AServerStoreCfg (..), AStoreType (..), import Simplex.Messaging.Server.Expiration import Simplex.Messaging.Server.MsgStore.Types (SMSType (..), SQSType (..)) import Simplex.Messaging.Server.QueueStore.QueueInfo -import Simplex.Messaging.Transport (ATransport (..), SMPVersion, VersionSMP, authCmdsSMPVersion, currentServerSMPRelayVersion, minClientSMPRelayVersion, minServerSMPRelayVersion, sendingProxySMPVersion, sndAuthKeySMPVersion, supportedSMPHandshakes, supportedServerSMPRelayVRange) +import Simplex.Messaging.Transport (ASrvTransport, SMPVersion, VersionSMP, authCmdsSMPVersion, currentServerSMPRelayVersion, minClientSMPRelayVersion, minServerSMPRelayVersion, sendingProxySMPVersion, sndAuthKeySMPVersion, supportedSMPHandshakes, supportedServerSMPRelayVRange) import Simplex.Messaging.Util (bshow, diffToMicroseconds) import Simplex.Messaging.Version (VersionRange (..)) import qualified Simplex.Messaging.Version as V @@ -267,7 +267,7 @@ sendMessage c connId msgFlags msgBody = do liftIO $ pqEnc `shouldBe` PQEncOn pure msgId -functionalAPITests :: (ATransport, AStoreType) -> Spec +functionalAPITests :: (ASrvTransport, AStoreType) -> Spec functionalAPITests ps = do describe "Establishing duplex connection" $ do testMatrix2 ps runAgentClientTest @@ -486,7 +486,7 @@ functionalAPITests ps = do it "server should respond with queue and subscription information" $ withSmpServer ps testServerQueueInfo -testBasicAuth :: (ATransport, AStoreType) -> Bool -> (Maybe BasicAuth, VersionSMP) -> (Maybe BasicAuth, VersionSMP) -> (Maybe BasicAuth, VersionSMP) -> SndQueueSecured -> AgentMsgId -> IO Int +testBasicAuth :: (ASrvTransport, AStoreType) -> Bool -> (Maybe BasicAuth, VersionSMP) -> (Maybe BasicAuth, VersionSMP) -> (Maybe BasicAuth, VersionSMP) -> SndQueueSecured -> AgentMsgId -> IO Int testBasicAuth (t, msType) allowNewQueues srv@(srvAuth, srvVersion) clnt1 clnt2 sqSecured baseId = do let testCfg = (cfgMS msType) {allowNewQueues, newQueueBasicAuth = srvAuth, smpServerVRange = V.mkVersionRange minServerSMPRelayVersion srvVersion} canCreate1 = canCreateQueue allowNewQueues srv clnt1 @@ -503,7 +503,7 @@ canCreateQueue :: Bool -> (Maybe BasicAuth, VersionSMP) -> (Maybe BasicAuth, Ver canCreateQueue allowNew (srvAuth, _) (clntAuth, _) = allowNew && (isNothing srvAuth || srvAuth == clntAuth) -testMatrix2 :: HasCallStack => (ATransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec +testMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec testMatrix2 ps runTest = do it "current, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 agentCfg agentCfg initAgentServersProxy 1 $ runTest PQSupportOn True True it "v8, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 agentProxyCfgV8 agentProxyCfgV8 initAgentServersProxy 3 $ runTest PQSupportOn False True @@ -512,7 +512,7 @@ testMatrix2 ps runTest = do it "prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgVPrev agentCfg 1 $ runTest PQSupportOff False False it "current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgVPrev 1 $ runTest PQSupportOff False False -testMatrix2Stress :: HasCallStack => (ATransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec +testMatrix2Stress :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec testMatrix2Stress ps runTest = do it "current, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 aCfg aCfg initAgentServersProxy 1 $ runTest PQSupportOn True True it "v8, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 aProxyCfgV8 aProxyCfgV8 initAgentServersProxy 1 $ runTest PQSupportOn False True @@ -525,14 +525,14 @@ testMatrix2Stress ps runTest = do aProxyCfgV8 = agentProxyCfgV8 {messageRetryInterval = fastMessageRetryInterval} aCfgVPrev = agentCfgVPrev {messageRetryInterval = fastMessageRetryInterval} -testBasicMatrix2 :: HasCallStack => (ATransport, AStoreType) -> (SndQueueSecured -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec +testBasicMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (SndQueueSecured -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec testBasicMatrix2 ps runTest = do it "current" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfg 1 $ runTest True it "prev" $ withSmpServer ps $ runTestCfg2 agentCfgVPrevPQ agentCfgVPrevPQ 1 $ runTest False it "prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgVPrevPQ agentCfg 1 $ runTest False it "current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgVPrevPQ 1 $ runTest False -testRatchetMatrix2 :: HasCallStack => (ATransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec +testRatchetMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec testRatchetMatrix2 ps runTest = do it "current, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 agentCfg agentCfg initAgentServersProxy 1 $ runTest PQSupportOn True True it "v8, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 agentProxyCfgV8 agentProxyCfgV8 initAgentServersProxy 3 $ runTest PQSupportOn False True @@ -541,17 +541,17 @@ testRatchetMatrix2 ps runTest = do it "ratchets prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgRatchetVPrev agentCfg 1 $ runTest PQSupportOff True False it "ratchets current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgRatchetVPrev 1 $ runTest PQSupportOff True False -testServerMatrix2 :: HasCallStack => (ATransport, AStoreType) -> (InitialAgentServers -> IO ()) -> Spec +testServerMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (InitialAgentServers -> IO ()) -> Spec testServerMatrix2 ps runTest = do it "1 server" $ withSmpServer ps $ runTest initAgentServers it "2 servers" $ withSmpServers2 ps $ runTest initAgentServers2 -testProxyMatrix :: HasCallStack => (ATransport, AStoreType) -> (Bool -> AgentClient -> AgentClient -> IO ()) -> Spec +testProxyMatrix :: HasCallStack => (ASrvTransport, AStoreType) -> (Bool -> AgentClient -> AgentClient -> IO ()) -> Spec testProxyMatrix ps runTest = do it "2 servers, directly" $ withSmpServers2 ps $ withAgentClientsServers2 (agentCfg, initAgentServers) (agentCfg, initAgentServers2) $ runTest False it "2 servers, via proxy" $ withSmpServersProxy2 ps $ withAgentClientsServers2 (agentCfg, initAgentServersProxy) (agentCfg, initAgentServersProxy2) $ runTest True -testProxyMatrixWithPrev :: HasCallStack => (ATransport, AStoreType) -> (Bool -> Bool -> AgentClient -> AgentClient -> IO ()) -> Spec +testProxyMatrixWithPrev :: HasCallStack => (ASrvTransport, AStoreType) -> (Bool -> Bool -> AgentClient -> AgentClient -> IO ()) -> Spec testProxyMatrixWithPrev ps@(t, msType@(ASType qs _ms)) runTest = do it "2 servers, directly, curr clients, prev servers" $ withSmpServers2Prev $ withAgentClientsServers2 (agentCfg, initAgentServers) (agentCfg, initAgentServers2) $ runTest False True it "2 servers, via proxy, curr clients, prev servers" $ withSmpServersProxy2Prev $ withAgentClientsServers2 (agentCfg, initAgentServersProxy) (agentCfg, initAgentServersProxy2) $ runTest True True @@ -564,13 +564,13 @@ testProxyMatrixWithPrev ps@(t, msType@(ASType qs _ms)) runTest = do withServers2 cfg1 cfg2 a = withSmpServerConfigOn t cfg1 testPort $ \_ -> withSmpServerConfigOn t cfg2 testPort2 $ \_ -> a -testPQMatrix2 :: HasCallStack => (ATransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec +testPQMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec testPQMatrix2 = pqMatrix2_ True -testPQMatrix2NoInv :: HasCallStack => (ATransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec +testPQMatrix2NoInv :: HasCallStack => (ASrvTransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec testPQMatrix2NoInv = pqMatrix2_ False -pqMatrix2_ :: HasCallStack => Bool -> (ATransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec +pqMatrix2_ :: HasCallStack => Bool -> (ASrvTransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec pqMatrix2_ pqInv ps test = do it "dh/dh handshake" $ runTest $ \a b -> test (a, IKPQOff) (b, PQSupportOff) it "dh/pq handshake" $ runTest $ \a b -> test (a, IKPQOff) (b, PQSupportOn) @@ -584,7 +584,7 @@ pqMatrix2_ pqInv ps test = do testPQMatrix3 :: HasCallStack => - (ATransport, AStoreType) -> + (ASrvTransport, AStoreType) -> (HasCallStack => (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()) -> Spec testPQMatrix3 ps test = do @@ -1047,7 +1047,7 @@ testAsyncBothOffline = do liftIO $ disposeAgentClient alice' liftIO $ disposeAgentClient bob' -testAsyncServerOffline :: HasCallStack => (ATransport, AStoreType) -> IO () +testAsyncServerOffline :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testAsyncServerOffline ps = withAgentClients2 $ \alice bob -> do -- create connection and shutdown the server (bobId, cReq) <- withSmpServerStoreLogOn ps testPort $ \_ -> @@ -1063,6 +1063,7 @@ testAsyncServerOffline ps = withAgentClients2 $ \alice bob -> do liftIO $ do srv1 `shouldBe` testSMPServer conns1 `shouldBe` [bobId] + liftIO $ threadDelay 250000 (aliceId, sqSecured) <- joinConnection bob 1 True cReq "bob's connInfo" SMSubscribe liftIO $ sqSecured `shouldBe` True ("", _, CONF confId _ "bob's connInfo") <- get alice @@ -1072,7 +1073,7 @@ testAsyncServerOffline ps = withAgentClients2 $ \alice bob -> do get bob ##> ("", aliceId, CON) exchangeGreetings alice bobId bob aliceId -testAllowConnectionClientRestart :: HasCallStack => (ATransport, AStoreType) -> IO () +testAllowConnectionClientRestart :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testAllowConnectionClientRestart ps@(t, ASType qsType _) = do let initAgentServersSrv2 = initAgentServers {smp = userServers [testSMPServer2]} alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB @@ -1253,7 +1254,7 @@ testAddContactShortLink viaProxy a b = connReq4 `shouldBe` connReq linkUserData updatedConnData' `shouldBe` updatedData -testInviationShortLinkRestart :: HasCallStack => (ATransport, AStoreType) -> IO () +testInviationShortLinkRestart :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testInviationShortLinkRestart ps = withAgentClients2 $ \a b -> do let userData = "some user data" (bId, CCLink connReq (Just shortLink)) <- withSmpServer ps $ @@ -1265,7 +1266,7 @@ testInviationShortLinkRestart ps = withAgentClients2 $ \a b -> do connReq' `shouldBe` connReq linkUserData connData' `shouldBe` userData -testContactShortLinkRestart :: HasCallStack => (ATransport, AStoreType) -> IO () +testContactShortLinkRestart :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testContactShortLinkRestart ps = withAgentClients2 $ \a b -> do let userData = "some user data" (contactId, CCLink connReq0 (Just shortLink)) <- withSmpServer ps $ @@ -1285,7 +1286,7 @@ testContactShortLinkRestart ps = withAgentClients2 $ \a b -> do connReq4 `shouldBe` connReq linkUserData updatedConnData' `shouldBe` updatedData -testAddContactShortLinkRestart :: HasCallStack => (ATransport, AStoreType) -> IO () +testAddContactShortLinkRestart :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testAddContactShortLinkRestart ps = withAgentClients2 $ \a b -> do let userData = "some user data" ((contactId, CCLink connReq0 Nothing), shortLink) <- withSmpServer ps $ runRight $ do @@ -1306,7 +1307,7 @@ testAddContactShortLinkRestart ps = withAgentClients2 $ \a b -> do connReq4 `shouldBe` connReq linkUserData updatedConnData' `shouldBe` updatedData -testIncreaseConnAgentVersion :: HasCallStack => (ATransport, AStoreType) -> IO () +testIncreaseConnAgentVersion :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testIncreaseConnAgentVersion ps = do alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB2 @@ -1371,7 +1372,7 @@ checkVersion c connId v = do ConnectionStats {connAgentVersion} <- getConnectionServers c connId liftIO $ connAgentVersion `shouldBe` VersionSMPA v -testIncreaseConnAgentVersionMaxCompatible :: HasCallStack => (ATransport, AStoreType) -> IO () +testIncreaseConnAgentVersionMaxCompatible :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testIncreaseConnAgentVersionMaxCompatible ps = do alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB2 @@ -1401,7 +1402,7 @@ testIncreaseConnAgentVersionMaxCompatible ps = do disposeAgentClient alice2 disposeAgentClient bob2 -testIncreaseConnAgentVersionStartDifferentVersion :: HasCallStack => (ATransport, AStoreType) -> IO () +testIncreaseConnAgentVersionStartDifferentVersion :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testIncreaseConnAgentVersionStartDifferentVersion ps = do alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2 @@ -1427,7 +1428,7 @@ testIncreaseConnAgentVersionStartDifferentVersion ps = do disposeAgentClient alice2 disposeAgentClient bob -testDeliverClientRestart :: HasCallStack => (ATransport, AStoreType) -> IO () +testDeliverClientRestart :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testDeliverClientRestart ps = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1458,7 +1459,7 @@ testDeliverClientRestart ps = do disposeAgentClient alice disposeAgentClient bob2 -testDuplicateMessage :: HasCallStack => (ATransport, AStoreType) -> IO () +testDuplicateMessage :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testDuplicateMessage ps = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1510,7 +1511,7 @@ testDuplicateMessage ps = do disposeAgentClient alice2 disposeAgentClient bob2 -testSkippedMessages :: HasCallStack => (ATransport, AStoreType) -> IO () +testSkippedMessages :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testSkippedMessages (t, msType) = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1561,7 +1562,7 @@ testSkippedMessages (t, msType) = do where cfg' = (cfgMS msType) {serverStoreCfg = ASSCfg SQSMemory SMSMemory $ SSCMemory $ Just $ StorePaths testStoreLogFile Nothing} -testDeliveryAfterSubscriptionError :: HasCallStack => (ATransport, AStoreType) -> IO () +testDeliveryAfterSubscriptionError :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testDeliveryAfterSubscriptionError ps = do (aId, bId) <- withAgentClients2 $ \a b -> do (aId, bId) <- withSmpServerStoreLogOn ps testPort $ \_ -> runRight $ makeConnection a b @@ -1579,7 +1580,7 @@ testDeliveryAfterSubscriptionError ps = do withUP b aId $ \case ("", c, Msg "hello") -> c == aId; _ -> False ackMessage b aId 2 Nothing -testMsgDeliveryQuotaExceeded :: HasCallStack => (ATransport, AStoreType) -> IO () +testMsgDeliveryQuotaExceeded :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testMsgDeliveryQuotaExceeded ps = withAgentClients2 $ \a b -> withSmpServerStoreLogOn ps testPort $ \_ -> runRight_ $ do (aId, bId) <- makeConnection a b @@ -1607,7 +1608,7 @@ testMsgDeliveryQuotaExceeded ps = get a =##> \case ("", c, SENT 6) -> bId == c; _ -> False liftIO $ concurrently_ (noMessages a "no more events") (noMessages b "no more events") -testExpireMessage :: HasCallStack => (ATransport, AStoreType) -> IO () +testExpireMessage :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testExpireMessage ps = withAgent 1 agentCfg {messageTimeout = 1.5, messageRetryInterval = fastMessageRetryInterval} initAgentServers testDB $ \a -> withAgent 2 agentCfg initAgentServers testDB2 $ \b -> do @@ -1623,7 +1624,7 @@ testExpireMessage ps = withUP b aId $ \case ("", _, MsgErr 2 (MsgSkipped 2 2) "2") -> True; _ -> False ackMessage b aId 2 Nothing -testExpireManyMessages :: HasCallStack => (ATransport, AStoreType) -> IO () +testExpireManyMessages :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testExpireManyMessages ps = withAgent 1 agentCfg {messageTimeout = 2, messageRetryInterval = fastMessageRetryInterval} initAgentServers testDB $ \a -> withAgent 2 agentCfg initAgentServers testDB2 $ \b -> do @@ -1662,7 +1663,7 @@ withUP a bId p = \case (corrId, c, AEvt SAEConn cmd) -> c == bId && p (corrId, c, cmd); _ -> False ] -testExpireMessageQuota :: HasCallStack => (ATransport, AStoreType) -> IO () +testExpireMessageQuota :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testExpireMessageQuota (t, msType) = withSmpServerConfigOn t (cfgMS msType) {msgQueueQuota = 1, maxJournalMsgCount = 2} testPort $ \_ -> do a <- getSMPAgentClient' 1 agentCfg {quotaExceededTimeout = 1, messageRetryInterval = fastMessageRetryInterval} initAgentServers testDB b <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1688,7 +1689,7 @@ testExpireMessageQuota (t, msType) = withSmpServerConfigOn t (cfgMS msType) {msg ackMessage b' aId 4 Nothing disposeAgentClient a -testExpireManyMessagesQuota :: (ATransport, AStoreType) -> IO () +testExpireManyMessagesQuota :: (ASrvTransport, AStoreType) -> IO () testExpireManyMessagesQuota (t, msType) = withSmpServerConfigOn t (cfgMS msType) {msgQueueQuota = 1, maxJournalMsgCount = 2} testPort $ \_ -> do a <- getSMPAgentClient' 1 agentCfg {quotaExceededTimeout = 2, messageRetryInterval = fastMessageRetryInterval} initAgentServers testDB b <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1725,7 +1726,7 @@ testExpireManyMessagesQuota (t, msType) = withSmpServerConfigOn t (cfgMS msType) ackMessage b' aId 4 Nothing disposeAgentClient a -testRatchetSync :: HasCallStack => (ATransport, AStoreType) -> IO () +testRatchetSync :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testRatchetSync ps = withAgentClients2 $ \alice bob -> withSmpServerStoreMsgLogOn ps testPort $ \_ -> do (aliceId, bobId, bob2) <- setupDesynchronizedRatchet alice bob @@ -1799,7 +1800,7 @@ ratchetSyncP' cId rss = \case cId' == cId && rss' == rss && ratchetSyncState == rss _ -> False -testRatchetSyncServerOffline :: HasCallStack => (ATransport, AStoreType) -> IO () +testRatchetSyncServerOffline :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testRatchetSyncServerOffline ps = withAgentClients2 $ \alice bob -> do (aliceId, bobId, bob2) <- withSmpServerStoreMsgLogOn ps testPort $ \_ -> setupDesynchronizedRatchet alice bob @@ -1825,7 +1826,7 @@ serverUpP = \case ("", "", AEvt SAENone (UP _ _)) -> True _ -> False -testRatchetSyncClientRestart :: HasCallStack => (ATransport, AStoreType) -> IO () +testRatchetSyncClientRestart :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testRatchetSyncClientRestart ps = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1850,7 +1851,7 @@ testRatchetSyncClientRestart ps = do disposeAgentClient bob disposeAgentClient bob3 -testRatchetSyncSuspendForeground :: HasCallStack => (ATransport, AStoreType) -> IO () +testRatchetSyncSuspendForeground :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testRatchetSyncSuspendForeground ps = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -1879,7 +1880,7 @@ testRatchetSyncSuspendForeground ps = do disposeAgentClient bob disposeAgentClient bob2 -testRatchetSyncSimultaneous :: HasCallStack => (ATransport, AStoreType) -> IO () +testRatchetSyncSimultaneous :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testRatchetSyncSimultaneous ps = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bob <- getSMPAgentClient' 2 agentCfg initAgentServers testDB2 @@ -2006,7 +2007,7 @@ makeConnectionForUsers_ pqSupport sqSecured alice aliceUserId bob bobUserId = do get bob ##> ("", aliceId, A.CON pqEnc) pure (aliceId, bobId) -testInactiveNoSubs :: (ATransport, AStoreType) -> IO () +testInactiveNoSubs :: (ASrvTransport, AStoreType) -> IO () testInactiveNoSubs (t, msType) = do let cfg' = (cfgMS msType) {inactiveClientExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 1}} withSmpServerConfigOn t cfg' testPort $ \_ -> @@ -2016,7 +2017,7 @@ testInactiveNoSubs (t, msType) = do Just (_, _, AEvt SAENone (DISCONNECT _ _)) <- timeout 5000000 $ atomically (readTBQueue $ subQ alice) pure () -testInactiveWithSubs :: (ATransport, AStoreType) -> IO () +testInactiveWithSubs :: (ASrvTransport, AStoreType) -> IO () testInactiveWithSubs (t, msType) = do let cfg' = (cfgMS msType) {inactiveClientExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 1}} withSmpServerConfigOn t cfg' testPort $ \_ -> @@ -2027,7 +2028,7 @@ testInactiveWithSubs (t, msType) = do -- and after 2 sec of inactivity no DOWN is sent as we have a live subscription liftIO $ timeout 1200000 (get alice) `shouldReturn` Nothing -testActiveClientNotDisconnected :: (ATransport, AStoreType) -> IO () +testActiveClientNotDisconnected :: (ASrvTransport, AStoreType) -> IO () testActiveClientNotDisconnected (t, msType) = do let cfg' = (cfgMS msType) {inactiveClientExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 1}} withSmpServerConfigOn t cfg' testPort $ \_ -> @@ -2070,7 +2071,7 @@ testSuspendingAgent = liftIO $ foregroundAgent b get b =##> \case ("", c, Msg "hello 2") -> c == aId; _ -> False -testSuspendingAgentCompleteSending :: (ATransport, AStoreType) -> IO () +testSuspendingAgentCompleteSending :: (ASrvTransport, AStoreType) -> IO () testSuspendingAgentCompleteSending ps = withAgentClients2 $ \a b -> do (aId, bId) <- withSmpServerStoreLogOn ps testPort $ \_ -> runRight $ do (aId, bId) <- makeConnection a b @@ -2101,7 +2102,7 @@ testSuspendingAgentCompleteSending ps = withAgentClients2 $ \a b -> do get a =##> \case ("", c, Msg "how are you?") -> c == bId; _ -> False ackMessage a bId 4 Nothing -testSuspendingAgentTimeout :: (ATransport, AStoreType) -> IO () +testSuspendingAgentTimeout :: (ASrvTransport, AStoreType) -> IO () testSuspendingAgentTimeout ps = withAgentClients2 $ \a b -> do (aId, _) <- withSmpServer ps . runRight $ do (aId, bId) <- makeConnection a b @@ -2120,7 +2121,7 @@ testSuspendingAgentTimeout ps = withAgentClients2 $ \a b -> do ("", "", SUSPENDED) <- nGet b pure () -testBatchedSubscriptions :: Int -> Int -> (ATransport, AStoreType) -> IO () +testBatchedSubscriptions :: Int -> Int -> (ASrvTransport, AStoreType) -> IO () testBatchedSubscriptions nCreate nDel ps@(t, ASType qsType _) = withAgentClientsCfgServers2 agentCfg agentCfg initAgentServers2 $ \a b -> do conns <- runServers $ do @@ -2312,7 +2313,7 @@ testAsyncCommands sqSecured alice bob baseId = where msgId = subtract baseId -testAsyncCommandsRestore :: (ATransport, AStoreType) -> IO () +testAsyncCommandsRestore :: (ASrvTransport, AStoreType) -> IO () testAsyncCommandsRestore ps = do alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB bobId <- runRight $ createConnectionAsync alice 1 "1" True SCMInvitation (IKNoPQ PQSupportOn) SMSubscribe @@ -2363,7 +2364,7 @@ testAcceptContactAsync sqSecured alice bob baseId = where msgId = subtract baseId -testDeleteConnectionAsync :: (ATransport, AStoreType) -> IO () +testDeleteConnectionAsync :: (ASrvTransport, AStoreType) -> IO () testDeleteConnectionAsync ps = withAgent 1 agentCfg {initialCleanupDelay = 10000, cleanupInterval = 10000, deleteErrorCount = 3} initAgentServers testDB $ \a -> do connIds <- withSmpServerStoreLogOn ps testPort $ \_ -> runRight $ do @@ -2379,7 +2380,7 @@ testDeleteConnectionAsync ps = get a =##> \case ("", "", DEL_CONNS cs) -> length cs == 3 && all (`elem` connIds) cs; _ -> False liftIO $ noMessages a "nothing else should be delivered to alice" -testWaitDeliveryNoPending :: (ATransport, AStoreType) -> IO () +testWaitDeliveryNoPending :: (ASrvTransport, AStoreType) -> IO () testWaitDeliveryNoPending ps = withAgentClients2 $ \alice bob -> withSmpServerStoreLogOn ps testPort $ \_ -> runRight_ $ do (aliceId, bobId) <- makeConnection alice bob @@ -2407,7 +2408,7 @@ testWaitDeliveryNoPending ps = withAgentClients2 $ \alice bob -> baseId = 1 msgId = subtract baseId -testWaitDelivery :: (ATransport, AStoreType) -> IO () +testWaitDelivery :: (ASrvTransport, AStoreType) -> IO () testWaitDelivery ps = withAgent 1 agentCfg {initialCleanupDelay = 10000, cleanupInterval = 10000, deleteErrorCount = 3} initAgentServers testDB $ \alice -> withAgent 2 agentCfg initAgentServers testDB2 $ \bob -> do @@ -2461,7 +2462,7 @@ testWaitDelivery ps = baseId = 1 msgId = subtract baseId -testWaitDeliveryAUTHErr :: (ATransport, AStoreType) -> IO () +testWaitDeliveryAUTHErr :: (ASrvTransport, AStoreType) -> IO () testWaitDeliveryAUTHErr ps = withAgent 1 agentCfg {initialCleanupDelay = 10000, cleanupInterval = 10000, deleteErrorCount = 3} initAgentServers testDB $ \alice -> withAgent 2 agentCfg initAgentServers testDB2 $ \bob -> do @@ -2504,7 +2505,7 @@ testWaitDeliveryAUTHErr ps = baseId = 1 msgId = subtract baseId -testWaitDeliveryTimeout :: (ATransport, AStoreType) -> IO () +testWaitDeliveryTimeout :: (ASrvTransport, AStoreType) -> IO () testWaitDeliveryTimeout ps = withAgent 1 agentCfg {connDeleteDeliveryTimeout = 1, initialCleanupDelay = 10000, cleanupInterval = 10000, deleteErrorCount = 3} initAgentServers testDB $ \alice -> withAgent 2 agentCfg initAgentServers testDB2 $ \bob -> do @@ -2544,7 +2545,7 @@ testWaitDeliveryTimeout ps = baseId = 1 msgId = subtract baseId -testWaitDeliveryTimeout2 :: (ATransport, AStoreType) -> IO () +testWaitDeliveryTimeout2 :: (ASrvTransport, AStoreType) -> IO () testWaitDeliveryTimeout2 ps = withAgent 1 agentCfg {connDeleteDeliveryTimeout = 2, messageRetryInterval = fastMessageRetryInterval, initialCleanupDelay = 10000, cleanupInterval = 10000, deleteErrorCount = 3} initAgentServers testDB $ \alice -> withAgent 2 agentCfg initAgentServers testDB2 $ \bob -> do @@ -2590,7 +2591,7 @@ testWaitDeliveryTimeout2 ps = baseId = 1 msgId = subtract baseId -testJoinConnectionAsyncReplyErrorV8 :: HasCallStack => (ATransport, AStoreType) -> IO () +testJoinConnectionAsyncReplyErrorV8 :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testJoinConnectionAsyncReplyErrorV8 ps@(t, ASType qsType _) = do let initAgentServersSrv2 = initAgentServers {smp = userServers [testSMPServer2]} withAgent 1 cfg' initAgentServers testDB $ \a -> @@ -2635,7 +2636,7 @@ testJoinConnectionAsyncReplyErrorV8 ps@(t, ASType qsType _) = do smpCfg = smpCfgVPrev {serverVRange = V.mkVersionRange minServerSMPRelayVersion sendingProxySMPVersion} -- before SKEY } -testJoinConnectionAsyncReplyError :: HasCallStack => (ATransport, AStoreType) -> IO () +testJoinConnectionAsyncReplyError :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testJoinConnectionAsyncReplyError ps@(t, ASType qsType _) = do let initAgentServersSrv2 = initAgentServers {smp = userServers [testSMPServer2]} withAgent 1 agentCfg initAgentServers testDB $ \a -> @@ -2702,7 +2703,7 @@ testDeleteUserQuietly = exchangeGreetingsMsgId 4 a bId b aId liftIO $ noMessages a "nothing else should be delivered to alice" -testUsersNoServer :: HasCallStack => (ATransport, AStoreType) -> IO () +testUsersNoServer :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testUsersNoServer ps = withAgentClientsCfg2 aCfg agentCfg $ \a b -> do (aId, bId, auId, _aId', bId') <- withSmpServerStoreLogOn ps testPort $ \_ -> runRight $ do (aId, bId) <- makeConnection a b @@ -3137,7 +3138,7 @@ testCreateQueueAuth srvVersion clnt1 clnt2 sqSecured baseId = do sndAuthAlg = if srvVersion >= authCmdsSMPVersion && clntVersion >= authCmdsSMPVersion then C.AuthAlg C.SX25519 else C.AuthAlg C.SEd25519 in getSMPAgentClient' clientId agentCfg {smpCfg, sndAuthAlg} servers db -testSMPServerConnectionTest :: (ATransport, AStoreType) -> Maybe BasicAuth -> SMPServerWithAuth -> IO (Maybe ProtocolTestFailure) +testSMPServerConnectionTest :: (ASrvTransport, AStoreType) -> Maybe BasicAuth -> SMPServerWithAuth -> IO (Maybe ProtocolTestFailure) testSMPServerConnectionTest (t, msType) newQueueBasicAuth srv = withSmpServerConfigOn t (cfgMS msType) {newQueueBasicAuth} testPort2 $ \_ -> do -- initially passed server is not running @@ -3172,7 +3173,7 @@ testDeliveryReceipts = ackMessage b aId 5 (Just "") `catchError` \case (A.CMD PROHIBITED _) -> pure (); e -> liftIO $ expectationFailure ("unexpected error " <> show e) ackMessage b aId 5 Nothing -testDeliveryReceiptsVersion :: HasCallStack => (ATransport, AStoreType) -> IO () +testDeliveryReceiptsVersion :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testDeliveryReceiptsVersion ps = do a <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB b <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2 @@ -3225,7 +3226,7 @@ testDeliveryReceiptsVersion ps = do disposeAgentClient a' disposeAgentClient b' -testDeliveryReceiptsConcurrent :: HasCallStack => (ATransport, AStoreType) -> IO () +testDeliveryReceiptsConcurrent :: HasCallStack => (ASrvTransport, AStoreType) -> IO () testDeliveryReceiptsConcurrent (t, msType) = withSmpServerConfigOn t (cfgMS msType) {msgQueueQuota = 256, maxJournalMsgCount = 512} testPort $ \_ -> do withAgentClients2 $ \a b -> do diff --git a/tests/AgentTests/NotificationTests.hs b/tests/AgentTests/NotificationTests.hs index 7ad997aea..9a202d980 100644 --- a/tests/AgentTests/NotificationTests.hs +++ b/tests/AgentTests/NotificationTests.hs @@ -81,7 +81,7 @@ import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Protocol (ErrorType (AUTH), MsgFlags (MsgFlags), NMsgMeta (..), NtfServer, ProtocolServer (..), SMPMsgMeta (..), SubscriptionMode (..)) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Server.Env.STM (AStoreType (..), ServerConfig (..)) -import Simplex.Messaging.Transport (ATransport) +import Simplex.Messaging.Transport (ASrvTransport) import System.Process (callCommand) import Test.Hspec hiding (fit, it) import UnliftIO @@ -92,7 +92,7 @@ import Database.PostgreSQL.Simple.SqlQQ (sql) import Database.SQLite.Simple.QQ (sql) #endif -notificationTests :: (ATransport, AStoreType) -> Spec +notificationTests :: (ASrvTransport, AStoreType) -> Spec notificationTests ps@(t, _) = do describe "Managing notification tokens" $ do it "should register and verify notification token" $ @@ -176,7 +176,7 @@ notificationTests ps@(t, _) = do withNtfServerOn t ntfTestPort2 ntfTestDBCfg2 . withNtfServerThreadOn t ntfTestPort ntfTestDBCfg $ \ntf -> testNotificationsNewToken apns ntf -testNtfMatrix :: HasCallStack => (ATransport, AStoreType) -> (APNSMockServer -> AgentMsgId -> AgentClient -> AgentClient -> IO ()) -> Spec +testNtfMatrix :: HasCallStack => (ASrvTransport, AStoreType) -> (APNSMockServer -> AgentMsgId -> AgentClient -> AgentClient -> IO ()) -> Spec testNtfMatrix ps@(_, msType) runTest = do describe "next and current" $ do it "curr servers; curr clients" $ runNtfTestCfg ps 1 cfg' ntfServerCfg agentCfg agentCfg runTest @@ -193,7 +193,7 @@ testNtfMatrix ps@(_, msType) runTest = do cfg' = cfgMS msType cfgVPrev' = cfgVPrev msType -runNtfTestCfg :: HasCallStack => (ATransport, AStoreType) -> AgentMsgId -> ServerConfig -> NtfServerConfig -> AgentConfig -> AgentConfig -> (APNSMockServer -> AgentMsgId -> AgentClient -> AgentClient -> IO ()) -> IO () +runNtfTestCfg :: HasCallStack => (ASrvTransport, AStoreType) -> AgentMsgId -> ServerConfig -> NtfServerConfig -> AgentConfig -> AgentConfig -> (APNSMockServer -> AgentMsgId -> AgentClient -> AgentClient -> IO ()) -> IO () runNtfTestCfg (t, msType) baseId smpCfg ntfCfg aCfg bCfg runTest = do let smpCfg' = smpCfg {serverStoreCfg = serverStoreConfig msType} withSmpServerConfigOn t smpCfg' testPort $ \_ -> @@ -275,7 +275,7 @@ testNtfTokenSecondRegistration apns = NTActive <- checkNtfToken a' tkn pure () -testNtfTokenServerRestart :: ATransport -> APNSMockServer -> IO () +testNtfTokenServerRestart :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenServerRestart t apns = do let tkn = DeviceToken PPApnsTest "abcd" ntfData <- withAgent 1 agentCfg initAgentServers testDB $ \a -> @@ -296,7 +296,7 @@ testNtfTokenServerRestart t apns = do NTActive <- checkNtfToken a' tkn pure () -testNtfTokenServerRestartReverify :: ATransport -> APNSMockServer -> IO () +testNtfTokenServerRestartReverify :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenServerRestartReverify t apns = do let tkn = DeviceToken PPApnsTest "abcd" withAgent 1 agentCfg initAgentServers testDB $ \a -> do @@ -319,7 +319,7 @@ testNtfTokenServerRestartReverify t apns = do NTActive <- checkNtfToken a' tkn pure () -testNtfTokenServerRestartReverifyTimeout :: ATransport -> APNSMockServer -> IO () +testNtfTokenServerRestartReverifyTimeout :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenServerRestartReverifyTimeout t apns = do let tkn = DeviceToken PPApnsTest "abcd" withAgent 1 agentCfg initAgentServers testDB $ \a@AgentClient {agentEnv = Env {store}} -> do @@ -354,7 +354,7 @@ testNtfTokenServerRestartReverifyTimeout t apns = do NTActive <- checkNtfToken a' tkn pure () -testNtfTokenServerRestartReregister :: ATransport -> APNSMockServer -> IO () +testNtfTokenServerRestartReregister :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenServerRestartReregister t apns = do let tkn = DeviceToken PPApnsTest "abcd" withAgent 1 agentCfg initAgentServers testDB $ \a -> @@ -378,7 +378,7 @@ testNtfTokenServerRestartReregister t apns = do NTActive <- checkNtfToken a' tkn pure () -testNtfTokenServerRestartReregisterTimeout :: ATransport -> APNSMockServer -> IO () +testNtfTokenServerRestartReregisterTimeout :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenServerRestartReregisterTimeout t apns = do let tkn = DeviceToken PPApnsTest "abcd" withAgent 1 agentCfg initAgentServers testDB $ \a@AgentClient {agentEnv = Env {store}} -> do @@ -419,7 +419,7 @@ getTestNtfTokenPort a = Just NtfToken {ntfServer = ProtocolServer {port}} -> pure port Nothing -> error "no active NtfToken" -testNtfTokenMultipleServers :: ATransport -> APNSMockServer -> IO () +testNtfTokenMultipleServers :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenMultipleServers t apns = do let tkn = DeviceToken PPApnsTest "abcd" withAgent 1 agentCfg initAgentServers2 testDB $ \a -> @@ -443,7 +443,7 @@ testNtfTokenMultipleServers t apns = do Left _ <- tryError (checkNtfToken a tkn) pure () -testNtfTokenChangeServers :: ATransport -> APNSMockServer -> IO () +testNtfTokenChangeServers :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenChangeServers t apns = withNtfServerThreadOn t ntfTestPort ntfTestDBCfg $ \ntf -> do tkn1 <- withAgent 1 agentCfg initAgentServers testDB $ \a -> runRight $ do @@ -473,7 +473,7 @@ testNtfTokenChangeServers t apns = tkn <- registerTestToken a "qwer" NMInstant apns checkNtfToken a tkn >>= \r -> liftIO $ r `shouldBe` NTActive -testNtfTokenReRegisterInvalid :: ATransport -> APNSMockServer -> IO () +testNtfTokenReRegisterInvalid :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenReRegisterInvalid t apns = do tkn <- withNtfServer t $ do withAgent 1 agentCfg initAgentServers testDB $ \a -> runRight $ do @@ -504,7 +504,7 @@ replaceSubstringInFile filePath oldText newText = do let newContent = T.replace oldText newText content TIO.writeFile filePath newContent -testNtfTokenReRegisterInvalidOnCheck :: ATransport -> APNSMockServer -> IO () +testNtfTokenReRegisterInvalidOnCheck :: ASrvTransport -> APNSMockServer -> IO () testNtfTokenReRegisterInvalidOnCheck t apns = do tkn <- withNtfServer t $ do withAgent 1 agentCfg initAgentServers testDB $ \a -> runRight $ do @@ -529,7 +529,7 @@ testNtfTokenReRegisterInvalidOnCheck t apns = do NTActive <- checkNtfToken a tkn1 pure () -testRunNTFServerTests :: ATransport -> NtfServer -> IO (Maybe ProtocolTestFailure) +testRunNTFServerTests :: ASrvTransport -> NtfServer -> IO (Maybe ProtocolTestFailure) testRunNTFServerTests t srv = withNtfServer t $ withAgent 1 agentCfg initAgentServers testDB $ \a -> @@ -751,7 +751,7 @@ testChangeToken apns = withAgent 1 agentCfg initAgentServers testDB2 $ \bob -> d baseId = 1 msgId = subtract baseId -testNotificationsStoreLog :: (ATransport, AStoreType) -> APNSMockServer -> IO () +testNotificationsStoreLog :: (ASrvTransport, AStoreType) -> APNSMockServer -> IO () testNotificationsStoreLog ps@(t, _) apns = withAgentClients2 $ \alice bob -> do withSmpServerStoreMsgLogOn ps testPort $ \_ -> do (aliceId, bobId) <- withNtfServer t $ runRight $ do @@ -786,7 +786,7 @@ testNotificationsStoreLog ps@(t, _) apns = withAgentClients2 $ \alice bob -> do withNtfServer t $ runRight_ $ do void $ messageNotificationData alice apns -testNotificationsSMPRestart :: (ATransport, AStoreType) -> APNSMockServer -> IO () +testNotificationsSMPRestart :: (ASrvTransport, AStoreType) -> APNSMockServer -> IO () testNotificationsSMPRestart ps apns = withAgentClients2 $ \alice bob -> do (aliceId, bobId) <- withSmpServerStoreLogOn ps testPort $ \threadId -> runRight $ do (aliceId, bobId) <- makeConnection alice bob @@ -814,7 +814,7 @@ testNotificationsSMPRestart ps apns = withAgentClients2 $ \alice bob -> do get alice =##> \case ("", c, Msg "hello again") -> c == bobId; _ -> False liftIO $ killThread threadId -testNotificationsSMPRestartBatch :: Int -> (ATransport, AStoreType) -> APNSMockServer -> IO () +testNotificationsSMPRestartBatch :: Int -> (ASrvTransport, AStoreType) -> APNSMockServer -> IO () testNotificationsSMPRestartBatch n ps@(t, ASType qsType _) apns = withAgentClientsCfgServers2 agentCfg agentCfg initAgentServers2 $ \a b -> do threadDelay 1000000 diff --git a/tests/CLITests.hs b/tests/CLITests.hs index ceb4d9e6e..1e0fe105b 100644 --- a/tests/CLITests.hs +++ b/tests/CLITests.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NamedFieldPuns #-} @@ -23,7 +24,7 @@ import qualified Network.HTTP2.Client as H2 import Simplex.FileTransfer.Server.Main (xftpServerCLI) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Server.Main (smpServerCLI, smpServerCLI_) -import Simplex.Messaging.Transport (TLS (..), defaultSupportedParams, defaultSupportedParamsHTTPS, simplexMQVersion, supportedClientSMPRelayVRange) +import Simplex.Messaging.Transport (TLS (..), TransportPeer (..), defaultSupportedParams, defaultSupportedParamsHTTPS, simplexMQVersion, supportedClientSMPRelayVRange) import Simplex.Messaging.Transport.Client (TransportClientConfig (..), defaultTransportClientConfig, runTLSTransportClient, smpClientHandshake) import Simplex.Messaging.Transport.HTTP2 (HTTP2Body (..)) import qualified Simplex.Messaging.Transport.HTTP2.Client as HC @@ -192,9 +193,9 @@ smpServerTestStatic = do runRight_ . void $ smpClientHandshake tls Nothing caSMP supportedClientSMPRelayVRange False logDebug "Combined SMP works" where - getCerts :: TLS -> [X.Certificate] + getCerts :: TLS 'TClient -> [X.Certificate] getCerts tls = - let X.CertificateChain cc = tlsServerCerts tls + let X.CertificateChain cc = tlsPeerCert tls in map (X.signedObject . X.getSigned) cc #if defined(dbServerPostgres) diff --git a/tests/NtfClient.hs b/tests/NtfClient.hs index 5c624ee9d..76cf8f9d8 100644 --- a/tests/NtfClient.hs +++ b/tests/NtfClient.hs @@ -174,7 +174,7 @@ ntfServerCfgVPrev = smpCfg' = smpCfg smpAgentCfg' serverVRange' = serverVRange smpCfg' -withNtfServerThreadOn :: HasCallStack => ATransport -> ServiceName -> PostgresStoreCfg -> (HasCallStack => ThreadId -> IO a) -> IO a +withNtfServerThreadOn :: HasCallStack => ASrvTransport -> ServiceName -> PostgresStoreCfg -> (HasCallStack => ThreadId -> IO a) -> IO a withNtfServerThreadOn t port' dbStoreConfig = withNtfServerCfg ntfServerCfg {transports = [(port', t, False)], dbStoreConfig} @@ -187,10 +187,10 @@ withNtfServerCfg cfg@NtfServerConfig {transports} = (\started -> runNtfServerBlocking started cfg) (pure ()) -withNtfServerOn :: HasCallStack => ATransport -> ServiceName -> PostgresStoreCfg -> (HasCallStack => IO a) -> IO a +withNtfServerOn :: HasCallStack => ASrvTransport -> ServiceName -> PostgresStoreCfg -> (HasCallStack => IO a) -> IO a withNtfServerOn t port' dbStoreConfig = withNtfServerThreadOn t port' dbStoreConfig . const -withNtfServer :: HasCallStack => ATransport -> (HasCallStack => IO a) -> IO a +withNtfServer :: HasCallStack => ASrvTransport -> (HasCallStack => IO a) -> IO a withNtfServer t = withNtfServerOn t ntfTestPort ntfTestDBCfg runNtfTest :: forall c a. Transport c => (THandleNTF c 'TClient -> IO a) -> IO a @@ -199,7 +199,7 @@ runNtfTest test = withNtfServer (transport @c) $ testNtfClient test ntfServerTest :: forall c smp. (Transport c, Encoding smp) => - TProxy c -> + TProxy c 'TServer -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO (Maybe TransmissionAuth, ByteString, ByteString, NtfResponse) ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h @@ -213,7 +213,7 @@ ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h [(Nothing, _, (CorrId corrId, EntityId qId, Right cmd))] <- tGet h pure (Nothing, corrId, qId, cmd) -ntfTest :: Transport c => TProxy c -> (THandleNTF c 'TClient -> IO ()) -> Expectation +ntfTest :: Transport c => TProxy c 'TServer -> (THandleNTF c 'TClient -> IO ()) -> Expectation ntfTest _ test' = runNtfTest test' `shouldReturn` () data APNSMockRequest = APNSMockRequest diff --git a/tests/NtfServerTests.hs b/tests/NtfServerTests.hs index 987301ff4..b8709957d 100644 --- a/tests/NtfServerTests.hs +++ b/tests/NtfServerTests.hs @@ -47,14 +47,14 @@ import Test.Hspec hiding (fit, it) import UnliftIO.STM import Util -ntfServerTests :: ATransport -> Spec +ntfServerTests :: ASrvTransport -> Spec ntfServerTests t = do describe "Notifications server protocol syntax" $ ntfSyntaxTests t describe "Notification subscriptions (NKEY)" $ testNotificationSubscription t createNtfQueueNKEY -- describe "Notification subscriptions (NEW with ntf creds)" $ testNotificationSubscription t createNtfQueueNEW describe "Retried notification subscription" $ testRetriedNtfSubscription t -ntfSyntaxTests :: ATransport -> Spec +ntfSyntaxTests :: ASrvTransport -> Spec ntfSyntaxTests (ATransport t) = do it "unknown command" $ ("", "abcd", "1234", ('H', 'E', 'L', 'L', 'O')) >#> ("", "abcd", "1234", NRErr $ CMD UNKNOWN) describe "NEW" $ do @@ -97,7 +97,7 @@ v .-> key = let J.Object o = v in U.decodeLenient . encodeUtf8 <$> JT.parseEither (J..: key) o -testNotificationSubscription :: ATransport -> CreateQueueFunc -> Spec +testNotificationSubscription :: ASrvTransport -> CreateQueueFunc -> Spec testNotificationSubscription (ATransport t) createQueue = it "should create notification subscription and notify when message is received" $ do g <- C.newRandom @@ -180,7 +180,7 @@ testNotificationSubscription (ATransport t) createQueue = smpServer3 `shouldBe` srv notifierId3 `shouldBe` nId -testRetriedNtfSubscription :: ATransport -> Spec +testRetriedNtfSubscription :: ASrvTransport -> Spec testRetriedNtfSubscription (ATransport t) = it "should allow retrying to create notification subscription with the same token and key" $ do g <- C.newRandom diff --git a/tests/SMPClient.hs b/tests/SMPClient.hs index 7eb49fc38..6549e46dc 100644 --- a/tests/SMPClient.hs +++ b/tests/SMPClient.hs @@ -37,7 +37,6 @@ import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal import System.Environment (lookupEnv) import System.Info (os) -import System.Process (callCommand) import Test.Hspec hiding (fit, it) import UnliftIO.Concurrent import qualified UnliftIO.Exception as E @@ -282,20 +281,20 @@ proxyCfgJ2QS = \case proxyVRangeV8 :: VersionRangeSMP proxyVRangeV8 = mkVersionRange minServerSMPRelayVersion sendingProxySMPVersion -withSmpServerStoreMsgLogOn :: HasCallStack => (ATransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a +withSmpServerStoreMsgLogOn :: HasCallStack => (ASrvTransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a withSmpServerStoreMsgLogOn (t, msType) = withSmpServerConfigOn t (cfgMS msType) {storeNtfsFile = Just testStoreNtfsFile, serverStatsBackupFile = Just testServerStatsBackupFile} -withSmpServerStoreLogOn :: HasCallStack => (ATransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a +withSmpServerStoreLogOn :: HasCallStack => (ASrvTransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a withSmpServerStoreLogOn (t, msType) = withSmpServerConfigOn t (cfgMS msType) {serverStatsBackupFile = Just testServerStatsBackupFile} -withSmpServerConfigOn :: HasCallStack => ATransport -> ServerConfig -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a +withSmpServerConfigOn :: HasCallStack => ASrvTransport -> ServerConfig -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a withSmpServerConfigOn t cfg' port' = serverBracket (\started -> runSMPServerBlocking started cfg' {transports = [(port', t, False)]} Nothing) (threadDelay 10000) -withSmpServerThreadOn :: HasCallStack => (ATransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a +withSmpServerThreadOn :: HasCallStack => (ASrvTransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a withSmpServerThreadOn (t, msType) = withSmpServerConfigOn t (cfgMS msType) serverBracket :: HasCallStack => (TMVar Bool -> IO ()) -> IO () -> (HasCallStack => ThreadId -> IO a) -> IO a @@ -303,7 +302,7 @@ serverBracket process afterProcess f = do started <- newEmptyTMVarIO E.bracket (forkIOWithUnmask (\unmask -> unmask (process started) `E.catchAny` handleStartError started)) - (\t -> killThread t >> afterProcess >> waitFor started "stop" >> callCommand "sync") + (\t -> killThread t >> afterProcess >> waitFor started "stop") (\t -> waitFor started "start" >> f t >>= \r -> r <$ threadDelay 100000) where -- it putTMVar is called twise to unlock both parts of the bracket in case of start failure @@ -316,19 +315,19 @@ serverBracket process afterProcess f = do Nothing -> error $ "server did not " <> s _ -> pure () -withSmpServerOn :: HasCallStack => (ATransport, AStoreType) -> ServiceName -> IO a -> IO a +withSmpServerOn :: HasCallStack => (ASrvTransport, AStoreType) -> ServiceName -> IO a -> IO a withSmpServerOn ps port' = withSmpServerThreadOn ps port' . const -withSmpServer :: HasCallStack => (ATransport, AStoreType) -> IO a -> IO a +withSmpServer :: HasCallStack => (ASrvTransport, AStoreType) -> IO a -> IO a withSmpServer ps = withSmpServerOn ps testPort -withSmpServerProxy :: HasCallStack => (ATransport, AStoreType) -> IO a -> IO a +withSmpServerProxy :: HasCallStack => (ASrvTransport, AStoreType) -> IO a -> IO a withSmpServerProxy (t, msType) = withSmpServerConfigOn t (proxyCfgMS msType) testPort . const -withSmpServers2 :: HasCallStack => (ATransport, AStoreType) -> IO a -> IO a +withSmpServers2 :: HasCallStack => (ASrvTransport, AStoreType) -> IO a -> IO a withSmpServers2 ps@(t, ASType qs _ms) = withSmpServer ps . withSmpServerConfigOn t (cfgJ2QS qs) testPort2 . const -withSmpServersProxy2 :: HasCallStack => (ATransport, AStoreType) -> IO a -> IO a +withSmpServersProxy2 :: HasCallStack => (ASrvTransport, AStoreType) -> IO a -> IO a withSmpServersProxy2 ps@(t, ASType qs _ms) = withSmpServerProxy ps . withSmpServerConfigOn t (proxyCfgJ2QS qs) testPort2 . const runSmpTest :: forall c a. (HasCallStack, Transport c) => AStoreType -> (HasCallStack => THandleSMP c 'TClient -> IO a) -> IO a @@ -347,7 +346,7 @@ runSmpTestNCfg srvCfg clntVR nClients test = withSmpServerConfigOn (transport @c smpServerTest :: forall c smp. (Transport c, Encoding smp) => - TProxy c -> + TProxy c 'TServer -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO (Maybe TransmissionAuth, ByteString, ByteString, BrokerMsg) smpServerTest _ t = runSmpTest (ASType SQSMemory SMSJournal) $ \h -> tPut' h t >> tGet' h @@ -361,36 +360,36 @@ smpServerTest _ t = runSmpTest (ASType SQSMemory SMSJournal) $ \h -> tPut' h t > [(Nothing, _, (CorrId corrId, EntityId qId, Right cmd))] <- tGet h pure (Nothing, corrId, qId, cmd) -smpTest :: (HasCallStack, Transport c) => TProxy c -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest :: (HasCallStack, Transport c) => TProxy c 'TServer -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> IO ()) -> Expectation smpTest _ msType test' = runSmpTest msType test' `shouldReturn` () -smpTest' :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest' :: forall c. (HasCallStack, Transport c) => TProxy c 'TServer -> (HasCallStack => THandleSMP c 'TClient -> IO ()) -> Expectation smpTest' = (`smpTest` ASType SQSMemory SMSJournal) smpTestN :: (HasCallStack, Transport c) => AStoreType -> Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO ()) -> Expectation smpTestN msType n test' = runSmpTestN msType n test' `shouldReturn` () -smpTest2' :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest2' :: forall c. (HasCallStack, Transport c) => TProxy c 'TServer -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation smpTest2' = (`smpTest2` ASType SQSMemory SMSJournal) -smpTest2 :: forall c. (HasCallStack, Transport c) => TProxy c -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest2 :: forall c. (HasCallStack, Transport c) => TProxy c 'TServer -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation smpTest2 t msType = smpTest2Cfg (cfgMS msType) supportedClientSMPRelayVRange t -smpTest2Cfg :: forall c. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest2Cfg :: forall c. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> TProxy c 'TServer -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation smpTest2Cfg srvCfg clntVR _ test' = runSmpTestNCfg srvCfg clntVR 2 _test `shouldReturn` () where _test :: HasCallStack => [THandleSMP c 'TClient] -> IO () _test [h1, h2] = test' h1 h2 _test _ = error "expected 2 handles" -smpTest3 :: forall c. (HasCallStack, Transport c) => TProxy c -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest3 :: forall c. (HasCallStack, Transport c) => TProxy c 'TServer -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation smpTest3 _ msType test' = smpTestN msType 3 _test where _test :: HasCallStack => [THandleSMP c 'TClient] -> IO () _test [h1, h2, h3] = test' h1 h2 h3 _test _ = error "expected 3 handles" -smpTest4 :: forall c. (HasCallStack, Transport c) => TProxy c -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation +smpTest4 :: forall c. (HasCallStack, Transport c) => TProxy c 'TServer -> AStoreType -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation smpTest4 _ msType test' = smpTestN msType 4 _test where _test :: HasCallStack => [THandleSMP c 'TClient] -> IO () diff --git a/tests/ServerTests.hs b/tests/ServerTests.hs index 14b73c1a4..34c2c9b34 100644 --- a/tests/ServerTests.hs +++ b/tests/ServerTests.hs @@ -53,7 +53,7 @@ import Test.HUnit import Test.Hspec hiding (fit, it) import Util -serverTests :: SpecWith (ATransport, AStoreType) +serverTests :: SpecWith (ASrvTransport, AStoreType) serverTests = do describe "SMP queues" $ do describe "NEW and KEY commands, SEND messages" testCreateSecure @@ -147,7 +147,7 @@ decryptMsgV3 dhShared nonce body = Right ClientRcvMsgQuota {} -> Left "ClientRcvMsgQuota" Left e -> Left e -testCreateSecure :: SpecWith (ATransport, AStoreType) +testCreateSecure :: SpecWith (ASrvTransport, AStoreType) testCreateSecure = it "should create (NEW) and secure (KEY) queue" $ \(ATransport t, msType) -> smpTest2 t msType $ \r s -> do @@ -212,7 +212,7 @@ testCreateSecure = Resp "bcda" _ (ERR LARGE_MSG) <- signSendRecv s sKey ("bcda", sId, _SEND biggerMessage) pure () -testCreateSndSecure :: SpecWith (ATransport, AStoreType) +testCreateSndSecure :: SpecWith (ASrvTransport, AStoreType) testCreateSndSecure = it "should create (NEW) and secure (SKEY) queue by sender" $ \(ATransport t, msType) -> smpTest2 t msType $ \r s -> do @@ -259,7 +259,7 @@ testCreateSndSecure = Resp "bcda" _ (ERR LARGE_MSG) <- signSendRecv s sKey ("bcda", sId, _SEND biggerMessage) pure () -testSndSecureProhibited :: SpecWith (ATransport, AStoreType) +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 @@ -274,7 +274,7 @@ testSndSecureProhibited = (sId2, sId) #== "secures queue, same queue ID in response" (err, ERR AUTH) #== "rejects SKEY when not allowed in NEW command" -testCreateUpdateKeys :: SpecWith (ATransport, AStoreType) +testCreateUpdateKeys :: SpecWith (ASrvTransport, AStoreType) testCreateUpdateKeys = it "should create (NEW) and updated recipient keys (RKEY)" $ \(ATransport t, msType) -> smpTest t msType $ \h -> do @@ -306,7 +306,7 @@ testCreateUpdateKeys = Resp "11" _ (INFO _) <- signSendRecv h rKey' ("11", rId, QUE) pure () -testCreateDelete :: SpecWith (ATransport, AStoreType) +testCreateDelete :: SpecWith (ASrvTransport, AStoreType) testCreateDelete = it "should create (NEW), suspend (OFF) and delete (DEL) queue" $ \(ATransport t, msType) -> smpTest2 t msType $ \rh sh -> do @@ -377,7 +377,7 @@ testCreateDelete = Resp "cdab" _ err10 <- signSendRecv rh rKey ("cdab", rId, SUB) (err10, ERR AUTH) #== "rejects SUB when deleted" -stressTest :: SpecWith (ATransport, AStoreType) +stressTest :: SpecWith (ASrvTransport, AStoreType) stressTest = it "should create many queues, disconnect and re-connect" $ \(ATransport t, msType) -> smpTest3 t msType $ \h1 h2 h3 -> do @@ -395,9 +395,9 @@ stressTest = closeConnection $ connection h2 subscribeQueues h3 -testAllowNewQueues :: SpecWith (ATransport, AStoreType) +testAllowNewQueues :: SpecWith (ASrvTransport, AStoreType) testAllowNewQueues = - it "should prohibit creating new queues with allowNewQueues = False" $ \(ATransport (t :: TProxy c), msType) -> + it "should prohibit creating new queues with allowNewQueues = False" $ \(ATransport (t :: TProxy c 'TServer), msType) -> withSmpServerConfigOn (ATransport t) (cfgMS msType) {allowNewQueues = False} testPort $ \_ -> testSMPClient @c $ \h -> do g <- C.newRandom @@ -406,7 +406,7 @@ testAllowNewQueues = Resp "abcd" NoEntity (ERR AUTH) <- signSendRecv h rKey ("abcd", NoEntity, New rPub dhPub) pure () -testDuplex :: SpecWith (ATransport, AStoreType) +testDuplex :: SpecWith (ASrvTransport, AStoreType) testDuplex = it "should create 2 simplex connections and exchange messages" $ \(ATransport t, msType) -> smpTest2 t msType $ \alice bob -> do @@ -461,7 +461,7 @@ testDuplex = Resp "bcda" _ OK <- signSendRecv bob brKey ("bcda", bRcv, ACK mId5) (bDec mId5 msg5, Right "how are you bob") #== "message received from alice" -testSwitchSub :: SpecWith (ATransport, AStoreType) +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 @@ -506,9 +506,9 @@ testSwitchSub = Nothing -> return () Just _ -> error "nothing else is delivered to the 1st TCP connection" -testGetCommand :: SpecWith (ATransport, AStoreType) +testGetCommand :: SpecWith (ASrvTransport, AStoreType) testGetCommand = - it "should retrieve messages from the queue using GET command" $ \(ATransport (t :: TProxy c), msType) -> do + 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 @@ -525,7 +525,7 @@ testGetCommand = Resp "4" _ OK <- signSendRecv rh rKey ("4", rId, GET) pure () -testGetSubCommands :: SpecWith (ATransport, AStoreType) +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 @@ -575,9 +575,9 @@ testGetSubCommands = Resp "12" _ OK <- signSendRecv rh2 rKey ("12", rId, GET) pure () -testExceedQueueQuota :: SpecWith (ATransport, AStoreType) +testExceedQueueQuota :: SpecWith (ASrvTransport, AStoreType) testExceedQueueQuota = - it "should reply with ERR QUOTA to sender and send QUOTA message to the recipient" $ \(ATransport (t :: TProxy c), msType) -> do + it "should reply with ERR QUOTA to sender and send QUOTA message to the recipient" $ \(ATransport (t :: TProxy c 'TServer), msType) -> do withSmpServerConfigOn (ATransport t) (cfgMS msType) {msgQueueQuota = 2} testPort $ \_ -> testSMPClient @c $ \sh -> testSMPClient @c $ \rh -> do g <- C.newRandom @@ -602,7 +602,7 @@ testExceedQueueQuota = Resp "10" _ OK <- signSendRecv rh rKey ("10", rId, ACK mId4) pure () -testWithStoreLog :: SpecWith (ATransport, AStoreType) +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 @@ -678,12 +678,12 @@ testWithStoreLog = logSize testStoreLogFile `shouldReturn` (if compacting then 1 else 6) removeFile testStoreLogFile where - runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation + 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 -> (THandleSMP c 'TClient -> IO ()) -> Expectation + runClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> Expectation runClient _ test' = testSMPClient test' `shouldReturn` () serverStoreLogCfg :: AStoreType -> (ServerConfig, Bool) @@ -705,7 +705,7 @@ logSize f = go (10 :: Int) | n > 0 -> threadDelay 100000 >> go (n - 1) | otherwise -> throwIO e -testRestoreMessages :: SpecWith (ATransport, AStoreType) +testRestoreMessages :: SpecWith (ASrvTransport, AStoreType) testRestoreMessages = it "should store messages on exit and restore on start" $ \(at@(ATransport t), msType) -> do removeFileIfExists testStoreLogFile @@ -783,12 +783,12 @@ testRestoreMessages = whenM (doesDirectoryExist testStoreMsgsDir) $ removeDirectoryRecursive testStoreMsgsDir removeFile testServerStatsBackupFile where - runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation + 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 -> (THandleSMP c 'TClient -> IO ()) -> Expectation + runClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> Expectation runClient _ test' = testSMPClient test' `shouldReturn` () checkStats :: ServerStatsData -> [RecipientId] -> Int -> Int -> Expectation @@ -807,7 +807,7 @@ checkStats s qs sent received = do IS.toList _week `shouldBe` map (hash . unEntityId) qs IS.toList _month `shouldBe` map (hash . unEntityId) qs -testRestoreExpireMessages :: SpecWith (ATransport, AStoreType) +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 @@ -869,15 +869,15 @@ testRestoreExpireMessages = removeFileIfExists testStoreMsgsFile exportMessages False ms testStoreMsgsFile False closeMsgStore ms - runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation + 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 -> (THandleSMP c 'TClient -> IO ()) -> Expectation + runClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO ()) -> Expectation runClient _ test' = testSMPClient test' `shouldReturn` () -testPrometheusMetrics :: SpecWith (ATransport, AStoreType) +testPrometheusMetrics :: SpecWith (ASrvTransport, AStoreType) testPrometheusMetrics = it "should save Prometheus metrics" $ \(at, msType) -> do let cfg' = (cfgMS msType) {prometheusInterval = Just 1} @@ -895,7 +895,7 @@ createAndSecureQueue h sPub = do (rId', rId) #== "same queue ID" pure (sId, rId, rKey, dhShared) -testTiming :: SpecWith (ATransport, AStoreType) +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 -> @@ -967,7 +967,7 @@ testTiming = ] ok `shouldBe` True -testMessageNotifications :: SpecWith (ATransport, AStoreType) +testMessageNotifications :: SpecWith (ASrvTransport, AStoreType) testMessageNotifications = it "should create simplex connection, subscribe notifier and deliver notifications" $ \(ATransport t, msType) -> do g <- C.newRandom @@ -1007,9 +1007,9 @@ testMessageNotifications = Nothing -> pure () Just _ -> error "nothing else should be delivered to the 2nd notifier's TCP connection" -testMsgExpireOnSend :: SpecWith (ATransport, AStoreType) +testMsgExpireOnSend :: SpecWith (ASrvTransport, AStoreType) testMsgExpireOnSend = - it "should expire messages that are not received before messageTTL on SEND" $ \(ATransport (t :: TProxy c), msType) -> do + 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' = (cfgMS msType) {messageExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 10000}} @@ -1027,10 +1027,10 @@ testMsgExpireOnSend = Nothing -> return () Just _ -> error "nothing else should be delivered" -testMsgExpireOnInterval :: SpecWith (ATransport, AStoreType) +testMsgExpireOnInterval :: SpecWith (ASrvTransport, AStoreType) testMsgExpireOnInterval = -- fails on ubuntu - xit' "should expire messages that are not received before messageTTL after expiry interval" $ \(ATransport (t :: TProxy c), msType) -> do + xit' "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' = (cfgMS msType) {messageExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 1}, idleQueueInterval = 1} @@ -1047,9 +1047,9 @@ testMsgExpireOnInterval = Nothing -> return () Just _ -> error "nothing should be delivered" -testMsgNOTExpireOnInterval :: SpecWith (ATransport, AStoreType) +testMsgNOTExpireOnInterval :: SpecWith (ASrvTransport, AStoreType) testMsgNOTExpireOnInterval = - it "should block and unblock message queues" $ \(ATransport (t :: TProxy c), msType) -> do + 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' = (cfgMS msType) {messageExpiration = Just ExpirationConfig {ttl = 1, checkInterval = 10000}} @@ -1066,10 +1066,10 @@ testMsgNOTExpireOnInterval = Nothing -> return () Just _ -> error "nothing else should be delivered" -testBlockMessageQueue :: SpecWith (ATransport, AStoreType) +testBlockMessageQueue :: SpecWith (ASrvTransport, AStoreType) testBlockMessageQueue = -- TODO [postgres] - xit "should return BLOCKED error when queue is blocked" $ \ps@(ATransport (t :: TProxy c), _) -> do + 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 @@ -1086,13 +1086,13 @@ testBlockMessageQueue = Resp "dabc" sId2 (ERR (BLOCKED (BlockingInfo BRContent))) <- signSendRecv h sKey ("dabc", sId, SKEY sPub) (sId2, sId) #== "same queue ID in response" where - runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO a) -> ThreadId -> IO a + 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 (ATransport, AStoreType) +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 @@ -1145,7 +1145,7 @@ testInvQueueLinkData = Resp "9" rId2 (ERR AUTH) <- signSendRecv r rKey ("9", rId, LDEL) rId2 `shouldBe` rId -testContactQueueLinkData :: SpecWith (ATransport, AStoreType) +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 @@ -1224,7 +1224,7 @@ instance Eq C.ASignature where Just Refl -> s == s' _ -> False -serverSyntaxTests :: ATransport -> Spec +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 diff --git a/tests/Util.hs b/tests/Util.hs index 75d596642..7ca759781 100644 --- a/tests/Util.hs +++ b/tests/Util.hs @@ -12,6 +12,7 @@ import Data.Either (partitionEithers) import Data.List (tails) import GHC.Conc (getNumCapabilities, getNumProcessors, setNumCapabilities) import System.Directory (doesFileExist, removeFile) +import System.Process (callCommand) import System.Timeout (timeout) import Test.Hspec hiding (fit, it) import qualified Test.Hspec as Hspec @@ -50,25 +51,28 @@ testLogLevel = LogError instance Example a => Example (TestWrapper a) where type Arg (TestWrapper a) = Arg a - evaluateExample (TestWrapper action) params hooks state = do - let tt = 120 - runTest = - timeout (tt * 1000000) (evaluateExample action params hooks state) >>= \case - Just r -> pure r - Nothing -> throwIO $ userError $ "test timed out after " <> show tt <> " seconds" - retryTest = do - putStrLn "Retrying with more logs..." - setLogLevel LogNote - runTest `finally` setLogLevel testLogLevel -- change this to match log level in Test.hs - E.try runTest >>= \case - Right r -> case resultStatus r of + evaluateExample (TestWrapper action) params hooks state = + runTest `E.catches` [E.Handler onTestFailure, E.Handler onTestException] + where + tt = 120 + runTest = + timeout (tt * 1000000) (evaluateExample action params hooks state) `finally` callCommand "sync" >>= \case + Just r -> pure r + Nothing -> throwIO $ userError $ "test timed out after " <> show tt <> " seconds" + onTestFailure :: ResultStatus -> IO Result + onTestFailure = \case Failure loc_ reason -> do putStrLn $ "Test failed: location " ++ show loc_ ++ ", reason: " ++ show reason retryTest - _ -> pure r - Left (e :: E.SomeException) -> do + r -> E.throwIO r + onTestException :: SomeException -> IO Result + onTestException e = do putStrLn $ "Test exception: " ++ show e retryTest + retryTest = do + putStrLn "Retrying with more logs..." + setLogLevel LogDebug + runTest `finally` setLogLevel testLogLevel -- change this to match log level in Test.hs it :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a) it label action = Hspec.it label (TestWrapper action)