mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-09-01 18:08:36 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33bd0ab42d | ||
|
|
3d40393ae8 | ||
|
|
b98fdb672d | ||
|
|
3ba3172aaf | ||
|
|
c00c223f3b | ||
|
|
2f43b43225 |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: simplexmq
|
name: simplexmq
|
||||||
version: 5.6.2.2
|
version: 5.7.0.0
|
||||||
synopsis: SimpleXMQ message broker
|
synopsis: SimpleXMQ message broker
|
||||||
description: |
|
description: |
|
||||||
This package includes <./docs/Simplex-Messaging-Server.html server>,
|
This package includes <./docs/Simplex-Messaging-Server.html server>,
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
|||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
name: simplexmq
|
name: simplexmq
|
||||||
version: 5.6.2.2
|
version: 5.7.0.0
|
||||||
synopsis: SimpleXMQ message broker
|
synopsis: SimpleXMQ message broker
|
||||||
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
|
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
|
||||||
<./docs/Simplex-Messaging-Client.html client> and
|
<./docs/Simplex-Messaging-Client.html client> and
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import Simplex.Messaging.Protocol
|
|||||||
RecipientId,
|
RecipientId,
|
||||||
SenderId,
|
SenderId,
|
||||||
)
|
)
|
||||||
import Simplex.Messaging.Transport (ALPN, HandshakeError (VERSION), THandleAuth (..), THandleParams (..), TransportError (..), supportedParameters)
|
import Simplex.Messaging.Transport (ALPN, HandshakeError (VERSION), THandleAuth (..), THandleParams (..), TransportError (..), TransportPeer (..), supportedParameters)
|
||||||
import Simplex.Messaging.Transport.Client (TransportClientConfig, TransportHost, alpn)
|
import Simplex.Messaging.Transport.Client (TransportClientConfig, TransportHost, alpn)
|
||||||
import Simplex.Messaging.Transport.HTTP2
|
import Simplex.Messaging.Transport.HTTP2
|
||||||
import Simplex.Messaging.Transport.HTTP2.Client
|
import Simplex.Messaging.Transport.HTTP2.Client
|
||||||
@@ -64,7 +64,7 @@ import UnliftIO.Directory
|
|||||||
data XFTPClient = XFTPClient
|
data XFTPClient = XFTPClient
|
||||||
{ http2Client :: HTTP2Client,
|
{ http2Client :: HTTP2Client,
|
||||||
transportSession :: TransportSession FileResponse,
|
transportSession :: TransportSession FileResponse,
|
||||||
thParams :: THandleParams XFTPVersion,
|
thParams :: THandleParams XFTPVersion 'TClient,
|
||||||
config :: XFTPClientConfig
|
config :: XFTPClientConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,8 +97,8 @@ defaultXFTPClientConfig =
|
|||||||
clientALPN = Just supportedXFTPhandshakes
|
clientALPN = Just supportedXFTPhandshakes
|
||||||
}
|
}
|
||||||
|
|
||||||
getXFTPClient :: TVar ChaChaDRG -> TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient)
|
getXFTPClient :: TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient)
|
||||||
getXFTPClient g transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN, xftpNetworkConfig, serverVRange} disconnected = runExceptT $ do
|
getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN, xftpNetworkConfig, serverVRange} disconnected = runExceptT $ do
|
||||||
let tcConfig = (transportClientConfig xftpNetworkConfig) {alpn = clientALPN}
|
let tcConfig = (transportClientConfig xftpNetworkConfig) {alpn = clientALPN}
|
||||||
http2Config = xftpHTTP2Config tcConfig config
|
http2Config = xftpHTTP2Config tcConfig config
|
||||||
username = proxyUsername transportSession
|
username = proxyUsername transportSession
|
||||||
@@ -112,7 +112,7 @@ getXFTPClient g transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN
|
|||||||
thParams0 = THandleParams {sessionId, blockSize = xftpBlockSize, thVersion = VersionXFTP 1, thAuth = Nothing, implySessId = False, batch = True}
|
thParams0 = THandleParams {sessionId, blockSize = xftpBlockSize, thVersion = VersionXFTP 1, thAuth = Nothing, implySessId = False, batch = True}
|
||||||
logDebug $ "Client negotiated handshake protocol: " <> tshow sessionALPN
|
logDebug $ "Client negotiated handshake protocol: " <> tshow sessionALPN
|
||||||
thParams@THandleParams {thVersion} <- case sessionALPN of
|
thParams@THandleParams {thVersion} <- case sessionALPN of
|
||||||
Just "xftp/1" -> xftpClientHandshakeV1 g serverVRange keyHash http2Client thParams0
|
Just "xftp/1" -> xftpClientHandshakeV1 serverVRange keyHash http2Client thParams0
|
||||||
Nothing -> pure thParams0
|
Nothing -> pure thParams0
|
||||||
_ -> throwError $ PCETransportError (TEHandshake VERSION)
|
_ -> throwError $ PCETransportError (TEHandshake VERSION)
|
||||||
logDebug $ "Client negotiated protocol: " <> tshow thVersion
|
logDebug $ "Client negotiated protocol: " <> tshow thVersion
|
||||||
@@ -120,19 +120,20 @@ getXFTPClient g transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN
|
|||||||
atomically $ writeTVar clientVar $ Just c
|
atomically $ writeTVar clientVar $ Just c
|
||||||
pure c
|
pure c
|
||||||
|
|
||||||
xftpClientHandshakeV1 :: TVar ChaChaDRG -> VersionRangeXFTP -> C.KeyHash -> HTTP2Client -> THandleParamsXFTP -> ExceptT XFTPClientError IO THandleParamsXFTP
|
xftpClientHandshakeV1 :: VersionRangeXFTP -> C.KeyHash -> HTTP2Client -> THandleParamsXFTP 'TClient -> ExceptT XFTPClientError IO (THandleParamsXFTP 'TClient)
|
||||||
xftpClientHandshakeV1 g serverVRange keyHash@(C.KeyHash kh) c@HTTP2Client {sessionId, serverKey} thParams0 = do
|
xftpClientHandshakeV1 serverVRange keyHash@(C.KeyHash kh) c@HTTP2Client {sessionId, serverKey} thParams0 = do
|
||||||
shs <- getServerHandshake
|
shs@XFTPServerHandshake {authPubKey = ck} <- getServerHandshake
|
||||||
(v, sk) <- processServerHandshake shs
|
(v, sk) <- processServerHandshake shs
|
||||||
(k, pk) <- atomically $ C.generateKeyPair g
|
sendClientHandshake XFTPClientHandshake {xftpVersion = v, keyHash}
|
||||||
sendClientHandshake XFTPClientHandshake {xftpVersion = v, keyHash, authPubKey = k}
|
pure thParams0 {thAuth = Just THAuthClient {serverPeerPubKey = sk, serverCertKey = ck, sessSecret = Nothing}, thVersion = v}
|
||||||
pure thParams0 {thAuth = Just THandleAuth {peerPubKey = sk, privKey = pk}, thVersion = v}
|
|
||||||
where
|
where
|
||||||
|
getServerHandshake :: ExceptT XFTPClientError IO XFTPServerHandshake
|
||||||
getServerHandshake = do
|
getServerHandshake = do
|
||||||
let helloReq = H.requestNoBody "POST" "/" []
|
let helloReq = H.requestNoBody "POST" "/" []
|
||||||
HTTP2Response {respBody = HTTP2Body {bodyHead = shsBody}} <-
|
HTTP2Response {respBody = HTTP2Body {bodyHead = shsBody}} <-
|
||||||
liftError' (const $ PCEResponseError HANDSHAKE) $ sendRequest c helloReq Nothing
|
liftError' (const $ PCEResponseError HANDSHAKE) $ sendRequest c helloReq Nothing
|
||||||
liftHS . smpDecode =<< liftHS (C.unPad shsBody)
|
liftHS . smpDecode =<< liftHS (C.unPad shsBody)
|
||||||
|
processServerHandshake :: XFTPServerHandshake -> ExceptT XFTPClientError IO (VersionXFTP, C.PublicKeyX25519)
|
||||||
processServerHandshake XFTPServerHandshake {xftpVersionRange, sessionId = serverSessId, authPubKey = serverAuth} = do
|
processServerHandshake XFTPServerHandshake {xftpVersionRange, sessionId = serverSessId, authPubKey = serverAuth} = do
|
||||||
unless (sessionId == serverSessId) $ throwError $ PCEResponseError SESSION
|
unless (sessionId == serverSessId) $ throwError $ PCEResponseError SESSION
|
||||||
case xftpVersionRange `compatibleVersion` serverVRange of
|
case xftpVersionRange `compatibleVersion` serverVRange of
|
||||||
@@ -145,6 +146,7 @@ xftpClientHandshakeV1 g serverVRange keyHash@(C.KeyHash kh) c@HTTP2Client {sessi
|
|||||||
_ -> throwError "bad certificate"
|
_ -> throwError "bad certificate"
|
||||||
pubKey <- maybe (throwError "bad server key type") (`C.verifyX509` exact) serverKey
|
pubKey <- maybe (throwError "bad server key type") (`C.verifyX509` exact) serverKey
|
||||||
C.x509ToPublic (pubKey, []) >>= C.pubKey
|
C.x509ToPublic (pubKey, []) >>= C.pubKey
|
||||||
|
sendClientHandshake :: XFTPClientHandshake -> ExceptT XFTPClientError IO ()
|
||||||
sendClientHandshake chs = do
|
sendClientHandshake chs = do
|
||||||
chs' <- liftHS $ C.pad (smpEncode chs) xftpBlockSize
|
chs' <- liftHS $ C.pad (smpEncode chs) xftpBlockSize
|
||||||
let chsReq = H.requestBuilder "POST" "/" [] $ byteString chs'
|
let chsReq = H.requestBuilder "POST" "/" [] $ byteString chs'
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import Control.Logger.Simple (logInfo)
|
|||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
import Control.Monad.Trans (lift)
|
import Control.Monad.Trans (lift)
|
||||||
import Crypto.Random (ChaChaDRG)
|
|
||||||
import Data.Bifunctor (first)
|
import Data.Bifunctor (first)
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
@@ -61,15 +60,15 @@ newXFTPAgent config = do
|
|||||||
|
|
||||||
type ME a = ExceptT XFTPClientAgentError IO a
|
type ME a = ExceptT XFTPClientAgentError IO a
|
||||||
|
|
||||||
getXFTPServerClient :: TVar ChaChaDRG -> XFTPClientAgent -> XFTPServer -> ME XFTPClient
|
getXFTPServerClient :: XFTPClientAgent -> XFTPServer -> ME XFTPClient
|
||||||
getXFTPServerClient g XFTPClientAgent {xftpClients, config} srv = do
|
getXFTPServerClient XFTPClientAgent {xftpClients, config} srv = do
|
||||||
atomically getClientVar >>= either newXFTPClient waitForXFTPClient
|
atomically getClientVar >>= either newXFTPClient waitForXFTPClient
|
||||||
where
|
where
|
||||||
connectClient :: ME XFTPClient
|
connectClient :: ME XFTPClient
|
||||||
connectClient =
|
connectClient =
|
||||||
ExceptT $
|
ExceptT $
|
||||||
first (XFTPClientAgentError srv)
|
first (XFTPClientAgentError srv)
|
||||||
<$> getXFTPClient g (1, srv, Nothing) (xftpConfig config) clientDisconnected
|
<$> getXFTPClient (1, srv, Nothing) (xftpConfig config) clientDisconnected
|
||||||
|
|
||||||
clientDisconnected :: XFTPClient -> IO ()
|
clientDisconnected :: XFTPClient -> IO ()
|
||||||
clientDisconnected _ = do
|
clientDisconnected _ = do
|
||||||
|
|||||||
@@ -333,9 +333,9 @@ cliSendFileOpts SendOptions {filePath, outputDir, numRecipients, xftpServers, re
|
|||||||
rKeys <- atomically $ L.fromList <$> replicateM numRecipients (C.generateAuthKeyPair C.SEd25519 g)
|
rKeys <- atomically $ L.fromList <$> replicateM numRecipients (C.generateAuthKeyPair C.SEd25519 g)
|
||||||
digest <- liftIO $ getChunkDigest chunkSpec
|
digest <- liftIO $ getChunkDigest chunkSpec
|
||||||
let ch = FileInfo {sndKey, size = fromIntegral chunkSize, digest}
|
let ch = FileInfo {sndKey, size = fromIntegral chunkSize, digest}
|
||||||
c <- withRetry retryCount $ getXFTPServerClient g a xftpServer
|
c <- withRetry retryCount $ getXFTPServerClient a xftpServer
|
||||||
(sndId, rIds) <- withRetry retryCount $ createXFTPChunk c spKey ch (L.map fst rKeys) auth
|
(sndId, rIds) <- withRetry retryCount $ createXFTPChunk c spKey ch (L.map fst rKeys) auth
|
||||||
withReconnect g a xftpServer retryCount $ \c' -> uploadXFTPChunk c' spKey sndId chunkSpec
|
withReconnect a xftpServer retryCount $ \c' -> uploadXFTPChunk c' spKey sndId chunkSpec
|
||||||
logInfo $ "uploaded chunk " <> tshow chunkNo
|
logInfo $ "uploaded chunk " <> tshow chunkNo
|
||||||
uploaded <- atomically . stateTVar uploadedChunks $ \cs ->
|
uploaded <- atomically . stateTVar uploadedChunks $ \cs ->
|
||||||
let cs' = fromIntegral chunkSize : cs in (sum cs', cs')
|
let cs' = fromIntegral chunkSize : cs in (sum cs', cs')
|
||||||
@@ -445,7 +445,7 @@ cliReceiveFile ReceiveOptions {fileDescription, filePath, retryCount, tempPath,
|
|||||||
when (FileSize encSize /= size) $ throwError $ CLIError "File size mismatch"
|
when (FileSize encSize /= size) $ throwError $ CLIError "File size mismatch"
|
||||||
liftIO $ printNoNewLine "Decrypting file..."
|
liftIO $ printNoNewLine "Decrypting file..."
|
||||||
CryptoFile path _ <- withExceptT cliCryptoError $ decryptChunks encSize chunkPaths key nonce $ fmap CF.plain . getFilePath
|
CryptoFile path _ <- withExceptT cliCryptoError $ decryptChunks encSize chunkPaths key nonce $ fmap CF.plain . getFilePath
|
||||||
forM_ chunks $ acknowledgeFileChunk g a
|
forM_ chunks $ acknowledgeFileChunk a
|
||||||
whenM (doesPathExist encPath) $ removeDirectoryRecursive encPath
|
whenM (doesPathExist encPath) $ removeDirectoryRecursive encPath
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
printNoNewLine $ "File downloaded: " <> path
|
printNoNewLine $ "File downloaded: " <> path
|
||||||
@@ -456,7 +456,7 @@ cliReceiveFile ReceiveOptions {fileDescription, filePath, retryCount, tempPath,
|
|||||||
logInfo $ "downloading chunk " <> tshow chunkNo <> " from " <> showServer server <> "..."
|
logInfo $ "downloading chunk " <> tshow chunkNo <> " from " <> showServer server <> "..."
|
||||||
chunkPath <- uniqueCombine encPath $ show chunkNo
|
chunkPath <- uniqueCombine encPath $ show chunkNo
|
||||||
let chunkSpec = XFTPRcvChunkSpec chunkPath (unFileSize chunkSize) (unFileDigest digest)
|
let chunkSpec = XFTPRcvChunkSpec chunkPath (unFileSize chunkSize) (unFileDigest digest)
|
||||||
withReconnect g a server retryCount $ \c -> downloadXFTPChunk g c replicaKey (unChunkReplicaId replicaId) chunkSpec
|
withReconnect a server retryCount $ \c -> downloadXFTPChunk g c replicaKey (unChunkReplicaId replicaId) chunkSpec
|
||||||
logInfo $ "downloaded chunk " <> tshow chunkNo <> " to " <> T.pack chunkPath
|
logInfo $ "downloaded chunk " <> tshow chunkNo <> " to " <> T.pack chunkPath
|
||||||
downloaded <- atomically . stateTVar downloadedChunks $ \cs ->
|
downloaded <- atomically . stateTVar downloadedChunks $ \cs ->
|
||||||
let cs' = fromIntegral (unFileSize chunkSize) : cs in (sum cs', cs')
|
let cs' = fromIntegral (unFileSize chunkSize) : cs in (sum cs', cs')
|
||||||
@@ -472,12 +472,12 @@ cliReceiveFile ReceiveOptions {fileDescription, filePath, retryCount, tempPath,
|
|||||||
ifM (doesDirectoryExist path) (uniqueCombine path name) $
|
ifM (doesDirectoryExist path) (uniqueCombine path name) $
|
||||||
ifM (doesFileExist path) (throwError "File already exists") (pure path)
|
ifM (doesFileExist path) (throwError "File already exists") (pure path)
|
||||||
_ -> (`uniqueCombine` name) . (</> "Downloads") =<< getHomeDirectory
|
_ -> (`uniqueCombine` name) . (</> "Downloads") =<< getHomeDirectory
|
||||||
acknowledgeFileChunk :: TVar ChaChaDRG -> XFTPClientAgent -> FileChunk -> ExceptT CLIError IO ()
|
acknowledgeFileChunk :: XFTPClientAgent -> FileChunk -> ExceptT CLIError IO ()
|
||||||
acknowledgeFileChunk g a FileChunk {replicas = replica : _} = do
|
acknowledgeFileChunk a FileChunk {replicas = replica : _} = do
|
||||||
let FileChunkReplica {server, replicaId, replicaKey} = replica
|
let FileChunkReplica {server, replicaId, replicaKey} = replica
|
||||||
c <- withRetry retryCount $ getXFTPServerClient g a server
|
c <- withRetry retryCount $ getXFTPServerClient a server
|
||||||
withRetry retryCount $ ackXFTPChunk c replicaKey (unChunkReplicaId replicaId)
|
withRetry retryCount $ ackXFTPChunk c replicaKey (unChunkReplicaId replicaId)
|
||||||
acknowledgeFileChunk _ _ _ = throwError $ CLIError "chunk has no replicas"
|
acknowledgeFileChunk _ _ = throwError $ CLIError "chunk has no replicas"
|
||||||
|
|
||||||
printProgress :: String -> Int64 -> Int64 -> IO ()
|
printProgress :: String -> Int64 -> Int64 -> IO ()
|
||||||
printProgress s part total = printNoNewLine $ s <> " " <> show ((part * 100) `div` total) <> "%"
|
printProgress s part total = printNoNewLine $ s <> " " <> show ((part * 100) `div` total) <> "%"
|
||||||
@@ -501,8 +501,7 @@ cliDeleteFile DeleteOptions {fileDescription, retryCount, yes} = do
|
|||||||
deleteFileChunk :: XFTPClientAgent -> FileChunk -> ExceptT CLIError IO ()
|
deleteFileChunk :: XFTPClientAgent -> FileChunk -> ExceptT CLIError IO ()
|
||||||
deleteFileChunk a FileChunk {chunkNo, replicas = replica : _} = do
|
deleteFileChunk a FileChunk {chunkNo, replicas = replica : _} = do
|
||||||
let FileChunkReplica {server, replicaId, replicaKey} = replica
|
let FileChunkReplica {server, replicaId, replicaKey} = replica
|
||||||
g <- liftIO C.newRandom
|
withReconnect a server retryCount $ \c -> deleteXFTPChunk c replicaKey (unChunkReplicaId replicaId)
|
||||||
withReconnect g a server retryCount $ \c -> deleteXFTPChunk c replicaKey (unChunkReplicaId replicaId)
|
|
||||||
logInfo $ "deleted chunk " <> tshow chunkNo <> " from " <> showServer server
|
logInfo $ "deleted chunk " <> tshow chunkNo <> " from " <> showServer server
|
||||||
deleteFileChunk _ _ = throwError $ CLIError "chunk has no replicas"
|
deleteFileChunk _ _ = throwError $ CLIError "chunk has no replicas"
|
||||||
|
|
||||||
@@ -570,9 +569,9 @@ prepareChunkSpecs filePath chunkSizes = reverse . snd $ foldl' addSpec (0, []) c
|
|||||||
getEncPath :: MonadIO m => Maybe FilePath -> String -> m FilePath
|
getEncPath :: MonadIO m => Maybe FilePath -> String -> m FilePath
|
||||||
getEncPath path name = (`uniqueCombine` (name <> ".encrypted")) =<< maybe (liftIO getCanonicalTemporaryDirectory) pure path
|
getEncPath path name = (`uniqueCombine` (name <> ".encrypted")) =<< maybe (liftIO getCanonicalTemporaryDirectory) pure path
|
||||||
|
|
||||||
withReconnect :: Show e => TVar ChaChaDRG -> XFTPClientAgent -> XFTPServer -> Int -> (XFTPClient -> ExceptT e IO a) -> ExceptT CLIError IO a
|
withReconnect :: Show e => XFTPClientAgent -> XFTPServer -> Int -> (XFTPClient -> ExceptT e IO a) -> ExceptT CLIError IO a
|
||||||
withReconnect g a srv n run = withRetry n $ do
|
withReconnect a srv n run = withRetry n $ do
|
||||||
c <- withRetry n $ getXFTPServerClient g a srv
|
c <- withRetry n $ getXFTPServerClient a srv
|
||||||
withExceptT (CLIError . show) (run c) `catchError` \e -> do
|
withExceptT (CLIError . show) (run c) `catchError` \e -> do
|
||||||
liftIO $ closeXFTPServerClient a srv
|
liftIO $ closeXFTPServerClient a srv
|
||||||
throwError e
|
throwError e
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ import Simplex.Messaging.Protocol
|
|||||||
ProtocolErrorType (..),
|
ProtocolErrorType (..),
|
||||||
ProtocolMsgTag (..),
|
ProtocolMsgTag (..),
|
||||||
ProtocolType (..),
|
ProtocolType (..),
|
||||||
RcvPublicDhKey,
|
|
||||||
RcvPublicAuthKey,
|
RcvPublicAuthKey,
|
||||||
|
RcvPublicDhKey,
|
||||||
RecipientId,
|
RecipientId,
|
||||||
SenderId,
|
SenderId,
|
||||||
SentRawTransmission,
|
SentRawTransmission,
|
||||||
@@ -48,14 +48,14 @@ import Simplex.Messaging.Protocol
|
|||||||
SndPublicAuthKey,
|
SndPublicAuthKey,
|
||||||
Transmission,
|
Transmission,
|
||||||
TransmissionForAuth (..),
|
TransmissionForAuth (..),
|
||||||
encodeTransmissionForAuth,
|
|
||||||
encodeTransmission,
|
encodeTransmission,
|
||||||
|
encodeTransmissionForAuth,
|
||||||
messageTagP,
|
messageTagP,
|
||||||
tDecodeParseValidate,
|
tDecodeParseValidate,
|
||||||
tEncodeBatch1,
|
tEncodeBatch1,
|
||||||
tParse,
|
tParse,
|
||||||
)
|
)
|
||||||
import Simplex.Messaging.Transport (THandleParams (..), TransportError (..))
|
import Simplex.Messaging.Transport (THandleParams (..), TransportError (..), TransportPeer (..))
|
||||||
import Simplex.Messaging.Util ((<$?>))
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
|
||||||
xftpBlockSize :: Int
|
xftpBlockSize :: Int
|
||||||
@@ -325,12 +325,12 @@ checkParty' c = case testEquality (sFileParty @p) (sFileParty @p') of
|
|||||||
Just Refl -> Just c
|
Just Refl -> Just c
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
xftpEncodeAuthTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion -> C.APrivateAuthKey -> Transmission c -> Either TransportError ByteString
|
xftpEncodeAuthTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion 'TClient -> C.APrivateAuthKey -> Transmission c -> Either TransportError ByteString
|
||||||
xftpEncodeAuthTransmission thParams@THandleParams {thAuth} pKey (corrId, fId, msg) = do
|
xftpEncodeAuthTransmission thParams@THandleParams {thAuth} pKey (corrId, fId, msg) = do
|
||||||
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, fId, msg)
|
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, fId, msg)
|
||||||
xftpEncodeBatch1 . (,tToSend) =<< authTransmission thAuth (Just pKey) corrId tForAuth
|
xftpEncodeBatch1 . (,tToSend) =<< authTransmission thAuth (Just pKey) corrId tForAuth
|
||||||
|
|
||||||
xftpEncodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion -> Transmission c -> Either TransportError ByteString
|
xftpEncodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion p -> Transmission c -> Either TransportError ByteString
|
||||||
xftpEncodeTransmission thParams (corrId, fId, msg) = do
|
xftpEncodeTransmission thParams (corrId, fId, msg) = do
|
||||||
let t = encodeTransmission thParams (corrId, fId, msg)
|
let t = encodeTransmission thParams (corrId, fId, msg)
|
||||||
xftpEncodeBatch1 (Nothing, t)
|
xftpEncodeBatch1 (Nothing, t)
|
||||||
@@ -339,7 +339,7 @@ xftpEncodeTransmission thParams (corrId, fId, msg) = do
|
|||||||
xftpEncodeBatch1 :: SentRawTransmission -> Either TransportError ByteString
|
xftpEncodeBatch1 :: SentRawTransmission -> Either TransportError ByteString
|
||||||
xftpEncodeBatch1 t = first (const TELargeMsg) $ C.pad (tEncodeBatch1 t) xftpBlockSize
|
xftpEncodeBatch1 t = first (const TELargeMsg) $ C.pad (tEncodeBatch1 t) xftpBlockSize
|
||||||
|
|
||||||
xftpDecodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion -> ByteString -> Either XFTPErrorType (SignedTransmission e c)
|
xftpDecodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion p -> ByteString -> Either XFTPErrorType (SignedTransmission e c)
|
||||||
xftpDecodeTransmission thParams t = do
|
xftpDecodeTransmission thParams t = do
|
||||||
t' <- first (const BLOCK) $ C.unPad t
|
t' <- first (const BLOCK) $ C.unPad t
|
||||||
case tParse thParams t' of
|
case tParse thParams t' of
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import Simplex.Messaging.Server.Expiration
|
|||||||
import Simplex.Messaging.Server.Stats
|
import Simplex.Messaging.Server.Stats
|
||||||
import Simplex.Messaging.TMap (TMap)
|
import Simplex.Messaging.TMap (TMap)
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport (SessionId, THandleAuth (..), THandleParams (..))
|
import Simplex.Messaging.Transport (SessionId, THandleAuth (..), THandleParams (..), TransportPeer (..))
|
||||||
import Simplex.Messaging.Transport.Buffer (trimCR)
|
import Simplex.Messaging.Transport.Buffer (trimCR)
|
||||||
import Simplex.Messaging.Transport.HTTP2
|
import Simplex.Messaging.Transport.HTTP2
|
||||||
import Simplex.Messaging.Transport.HTTP2.File (fileBlockSize)
|
import Simplex.Messaging.Transport.HTTP2.File (fileBlockSize)
|
||||||
@@ -75,7 +75,7 @@ import qualified UnliftIO.Exception as E
|
|||||||
type M a = ReaderT XFTPEnv IO a
|
type M a = ReaderT XFTPEnv IO a
|
||||||
|
|
||||||
data XFTPTransportRequest = XFTPTransportRequest
|
data XFTPTransportRequest = XFTPTransportRequest
|
||||||
{ thParams :: THandleParamsXFTP,
|
{ thParams :: THandleParamsXFTP 'TServer,
|
||||||
reqBody :: HTTP2Body,
|
reqBody :: HTTP2Body,
|
||||||
request :: H.Request,
|
request :: H.Request,
|
||||||
sendResponse :: H.Response -> IO ()
|
sendResponse :: H.Response -> IO ()
|
||||||
@@ -91,7 +91,7 @@ runXFTPServerBlocking started cfg = newXFTPServerEnv cfg >>= runReaderT (xftpSer
|
|||||||
|
|
||||||
data Handshake
|
data Handshake
|
||||||
= HandshakeSent C.PrivateKeyX25519
|
= HandshakeSent C.PrivateKeyX25519
|
||||||
| HandshakeAccepted THandleAuth VersionXFTP
|
| HandshakeAccepted (THandleAuth 'TServer) VersionXFTP
|
||||||
|
|
||||||
xftpServer :: XFTPServerConfig -> TMVar Bool -> M ()
|
xftpServer :: XFTPServerConfig -> TMVar Bool -> M ()
|
||||||
xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpiration, fileExpiration} started = do
|
xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpiration, fileExpiration} started = do
|
||||||
@@ -120,7 +120,7 @@ xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpira
|
|||||||
Nothing -> pure () -- handshake response sent
|
Nothing -> pure () -- handshake response sent
|
||||||
Just thParams -> processRequest req0 {thParams} -- proceed with new version (XXX: may as well switch the request handler here)
|
Just thParams -> processRequest req0 {thParams} -- proceed with new version (XXX: may as well switch the request handler here)
|
||||||
_ -> liftIO . sendResponse $ H.responseNoBody N.ok200 [] -- shouldn't happen: means server picked handshake protocol it doesn't know about
|
_ -> liftIO . sendResponse $ H.responseNoBody N.ok200 [] -- shouldn't happen: means server picked handshake protocol it doesn't know about
|
||||||
xftpServerHandshakeV1 :: X.CertificateChain -> C.APrivateSignKey -> TMap SessionId Handshake -> XFTPTransportRequest -> M (Maybe (THandleParams XFTPVersion))
|
xftpServerHandshakeV1 :: X.CertificateChain -> C.APrivateSignKey -> TMap SessionId Handshake -> XFTPTransportRequest -> M (Maybe (THandleParams XFTPVersion 'TServer))
|
||||||
xftpServerHandshakeV1 chain serverSignKey sessions XFTPTransportRequest {thParams = thParams@THandleParams {sessionId}, reqBody = HTTP2Body {bodyHead}, sendResponse} = do
|
xftpServerHandshakeV1 chain serverSignKey sessions XFTPTransportRequest {thParams = thParams@THandleParams {sessionId}, reqBody = HTTP2Body {bodyHead}, sendResponse} = do
|
||||||
s <- atomically $ TM.lookup sessionId sessions
|
s <- atomically $ TM.lookup sessionId sessions
|
||||||
r <- runExceptT $ case s of
|
r <- runExceptT $ case s of
|
||||||
@@ -138,18 +138,18 @@ xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpira
|
|||||||
shs <- encodeXftp hs
|
shs <- encodeXftp hs
|
||||||
liftIO . sendResponse $ H.responseBuilder N.ok200 [] shs
|
liftIO . sendResponse $ H.responseBuilder N.ok200 [] shs
|
||||||
pure Nothing
|
pure Nothing
|
||||||
processClientHandshake privKey = do
|
processClientHandshake pk = do
|
||||||
unless (B.length bodyHead == xftpBlockSize) $ throwError HANDSHAKE
|
unless (B.length bodyHead == xftpBlockSize) $ throwError HANDSHAKE
|
||||||
body <- liftHS $ C.unPad bodyHead
|
body <- liftHS $ C.unPad bodyHead
|
||||||
XFTPClientHandshake {xftpVersion, keyHash, authPubKey} <- liftHS $ smpDecode body
|
XFTPClientHandshake {xftpVersion, keyHash} <- liftHS $ smpDecode body
|
||||||
kh <- asks serverIdentity
|
kh <- asks serverIdentity
|
||||||
unless (keyHash == kh) $ throwError HANDSHAKE
|
unless (keyHash == kh) $ throwError HANDSHAKE
|
||||||
unless (xftpVersion `isCompatible` supportedFileServerVRange) $ throwError HANDSHAKE
|
unless (xftpVersion `isCompatible` supportedFileServerVRange) $ throwError HANDSHAKE
|
||||||
let auth = THandleAuth {peerPubKey = authPubKey, privKey}
|
let auth = THAuthServer {serverPrivKey = pk, sessSecret' = Nothing}
|
||||||
atomically $ TM.insert sessionId (HandshakeAccepted auth xftpVersion) sessions
|
atomically $ TM.insert sessionId (HandshakeAccepted auth xftpVersion) sessions
|
||||||
liftIO . sendResponse $ H.responseNoBody N.ok200 []
|
liftIO . sendResponse $ H.responseNoBody N.ok200 []
|
||||||
pure Nothing
|
pure Nothing
|
||||||
sendError :: XFTPErrorType -> M (Maybe (THandleParams XFTPVersion))
|
sendError :: XFTPErrorType -> M (Maybe (THandleParams XFTPVersion 'TServer))
|
||||||
sendError err = do
|
sendError err = do
|
||||||
runExceptT (encodeXftp err) >>= \case
|
runExceptT (encodeXftp err) >>= \case
|
||||||
Right bs -> liftIO . sendResponse $ H.responseBuilder N.ok200 [] bs
|
Right bs -> liftIO . sendResponse $ H.responseBuilder N.ok200 [] bs
|
||||||
@@ -326,7 +326,7 @@ processRequest XFTPTransportRequest {thParams, reqBody = body@HTTP2Body {bodyHea
|
|||||||
|
|
||||||
data VerificationResult = VRVerified XFTPRequest | VRFailed
|
data VerificationResult = VRVerified XFTPRequest | VRFailed
|
||||||
|
|
||||||
verifyXFTPTransmission :: Maybe (THandleAuth, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> XFTPFileId -> FileCmd -> M VerificationResult
|
verifyXFTPTransmission :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> XFTPFileId -> FileCmd -> M VerificationResult
|
||||||
verifyXFTPTransmission auth_ tAuth authorized fId cmd =
|
verifyXFTPTransmission auth_ tAuth authorized fId cmd =
|
||||||
case cmd of
|
case cmd of
|
||||||
FileCmd SFSender (FNEW file rcps auth') -> pure $ XFTPReqNew file rcps auth' `verifyWith` sndKey file
|
FileCmd SFSender (FNEW file rcps auth') -> pure $ XFTPReqNew file rcps auth' `verifyWith` sndKey file
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import Options.Applicative
|
|||||||
import Simplex.FileTransfer.Chunks
|
import Simplex.FileTransfer.Chunks
|
||||||
import Simplex.FileTransfer.Description (FileSize (..))
|
import Simplex.FileTransfer.Description (FileSize (..))
|
||||||
import Simplex.FileTransfer.Server (runXFTPServer)
|
import Simplex.FileTransfer.Server (runXFTPServer)
|
||||||
import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defFileExpirationHours, defaultFileExpiration, defaultInactiveClientExpiration)
|
import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defFileExpirationHours, defaultFileExpiration, defaultInactiveClientExpiration, supportedXFTPhandshakes)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), pattern XFTPServer)
|
import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), pattern XFTPServer)
|
||||||
@@ -180,7 +180,8 @@ xftpServerCLI cfgPath logPath = do
|
|||||||
serverStatsBackupFile = logStats $> combine logPath "file-server-stats.log",
|
serverStatsBackupFile = logStats $> combine logPath "file-server-stats.log",
|
||||||
transportConfig =
|
transportConfig =
|
||||||
defaultTransportServerConfig
|
defaultTransportServerConfig
|
||||||
{ logTLSErrors = fromMaybe False $ iniOnOff "TRANSPORT" "log_tls_errors" ini
|
{ logTLSErrors = fromMaybe False $ iniOnOff "TRANSPORT" "log_tls_errors" ini,
|
||||||
|
alpn = Just supportedXFTPhandshakes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE MultiWayIf #-}
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
|
|
||||||
module Simplex.FileTransfer.Transport
|
module Simplex.FileTransfer.Transport
|
||||||
( supportedFileServerVRange,
|
( supportedFileServerVRange,
|
||||||
|
authCmdsXFTPVersion,
|
||||||
xftpClientHandshakeStub,
|
xftpClientHandshakeStub,
|
||||||
XFTPClientHandshake (..),
|
XFTPClientHandshake (..),
|
||||||
-- xftpClientHandshake,
|
-- xftpClientHandshake,
|
||||||
@@ -51,7 +53,7 @@ import Simplex.Messaging.Encoding
|
|||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers
|
import Simplex.Messaging.Parsers
|
||||||
import Simplex.Messaging.Protocol (CommandError)
|
import Simplex.Messaging.Protocol (CommandError)
|
||||||
import Simplex.Messaging.Transport (HandshakeError (..), SessionId, THandle (..), THandleParams (..), TransportError (..))
|
import Simplex.Messaging.Transport (HandshakeError (..), SessionId, THandle (..), THandleParams (..), TransportError (..), TransportPeer (..))
|
||||||
import Simplex.Messaging.Transport.HTTP2.File
|
import Simplex.Messaging.Transport.HTTP2.File
|
||||||
import Simplex.Messaging.Util (bshow)
|
import Simplex.Messaging.Util (bshow)
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
@@ -76,20 +78,23 @@ type VersionRangeXFTP = VersionRange XFTPVersion
|
|||||||
pattern VersionXFTP :: Word16 -> VersionXFTP
|
pattern VersionXFTP :: Word16 -> VersionXFTP
|
||||||
pattern VersionXFTP v = Version v
|
pattern VersionXFTP v = Version v
|
||||||
|
|
||||||
type THandleXFTP c = THandle XFTPVersion c
|
type THandleXFTP c p = THandle XFTPVersion c p
|
||||||
type THandleParamsXFTP = THandleParams XFTPVersion
|
type THandleParamsXFTP p = THandleParams XFTPVersion p
|
||||||
|
|
||||||
initialXFTPVersion :: VersionXFTP
|
initialXFTPVersion :: VersionXFTP
|
||||||
initialXFTPVersion = VersionXFTP 1
|
initialXFTPVersion = VersionXFTP 1
|
||||||
|
|
||||||
|
authCmdsXFTPVersion :: VersionXFTP
|
||||||
|
authCmdsXFTPVersion = VersionXFTP 2
|
||||||
|
|
||||||
currentXFTPVersion :: VersionXFTP
|
currentXFTPVersion :: VersionXFTP
|
||||||
currentXFTPVersion = VersionXFTP 2
|
currentXFTPVersion = VersionXFTP 2
|
||||||
|
|
||||||
supportedFileServerVRange :: VersionRangeXFTP
|
supportedFileServerVRange :: VersionRangeXFTP
|
||||||
supportedFileServerVRange = mkVersionRange initialXFTPVersion currentXFTPVersion
|
supportedFileServerVRange = mkVersionRange initialXFTPVersion currentXFTPVersion
|
||||||
|
|
||||||
-- XFTP protocol does not support handshake
|
-- XFTP protocol does not use this handshake method
|
||||||
xftpClientHandshakeStub :: c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeXFTP -> ExceptT TransportError IO (THandle XFTPVersion c)
|
xftpClientHandshakeStub :: c -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeXFTP -> ExceptT TransportError IO (THandle XFTPVersion c 'TClient)
|
||||||
xftpClientHandshakeStub _c _ks _keyHash _xftpVRange = throwError $ TEHandshake VERSION
|
xftpClientHandshakeStub _c _ks _keyHash _xftpVRange = throwError $ TEHandshake VERSION
|
||||||
|
|
||||||
data XFTPServerHandshake = XFTPServerHandshake
|
data XFTPServerHandshake = XFTPServerHandshake
|
||||||
@@ -103,19 +108,16 @@ data XFTPClientHandshake = XFTPClientHandshake
|
|||||||
{ -- | agreed XFTP server protocol version
|
{ -- | agreed XFTP server protocol version
|
||||||
xftpVersion :: VersionXFTP,
|
xftpVersion :: VersionXFTP,
|
||||||
-- | server identity - CA certificate fingerprint
|
-- | server identity - CA certificate fingerprint
|
||||||
keyHash :: C.KeyHash,
|
keyHash :: C.KeyHash
|
||||||
-- | pub key to agree shared secret for entity ID encryption, shared secret for command authorization is agreed using per-queue keys.
|
|
||||||
authPubKey :: C.PublicKeyX25519
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instance Encoding XFTPClientHandshake where
|
instance Encoding XFTPClientHandshake where
|
||||||
smpEncode XFTPClientHandshake {xftpVersion, keyHash, authPubKey} =
|
smpEncode XFTPClientHandshake {xftpVersion, keyHash} =
|
||||||
smpEncode (xftpVersion, keyHash, authPubKey)
|
smpEncode (xftpVersion, keyHash)
|
||||||
smpP = do
|
smpP = do
|
||||||
(xftpVersion, keyHash) <- smpP
|
(xftpVersion, keyHash) <- smpP
|
||||||
authPubKey <- smpP
|
|
||||||
Tail _compat <- smpP
|
Tail _compat <- smpP
|
||||||
pure XFTPClientHandshake {xftpVersion, keyHash, authPubKey}
|
pure XFTPClientHandshake {xftpVersion, keyHash}
|
||||||
|
|
||||||
instance Encoding XFTPServerHandshake where
|
instance Encoding XFTPServerHandshake where
|
||||||
smpEncode XFTPServerHandshake {xftpVersionRange, sessionId, authPubKey} =
|
smpEncode XFTPServerHandshake {xftpVersionRange, sessionId, authPubKey} =
|
||||||
|
|||||||
@@ -688,10 +688,9 @@ getXFTPServerClient c@AgentClient {active, xftpClients, workerSeq} tSess@(userId
|
|||||||
connectClient :: XFTPClientVar -> AM XFTPClient
|
connectClient :: XFTPClientVar -> AM XFTPClient
|
||||||
connectClient v = do
|
connectClient v = do
|
||||||
cfg <- asks $ xftpCfg . config
|
cfg <- asks $ xftpCfg . config
|
||||||
g <- asks random
|
|
||||||
xftpNetworkConfig <- atomically $ getNetworkConfig c
|
xftpNetworkConfig <- atomically $ getNetworkConfig c
|
||||||
liftError' (protocolClientError XFTP $ B.unpack $ strEncode srv) $
|
liftError' (protocolClientError XFTP $ B.unpack $ strEncode srv) $
|
||||||
X.getXFTPClient g tSess cfg {xftpNetworkConfig} $
|
X.getXFTPClient tSess cfg {xftpNetworkConfig} $
|
||||||
clientDisconnected v
|
clientDisconnected v
|
||||||
|
|
||||||
clientDisconnected :: XFTPClientVar -> XFTPClient -> IO ()
|
clientDisconnected :: XFTPClientVar -> XFTPClient -> IO ()
|
||||||
@@ -703,8 +702,8 @@ getXFTPServerClient c@AgentClient {active, xftpClients, workerSeq} tSess@(userId
|
|||||||
|
|
||||||
waitForProtocolClient :: ProtocolTypeI (ProtoType msg) => AgentClient -> TransportSession msg -> ClientVar msg -> AM (Client msg)
|
waitForProtocolClient :: ProtocolTypeI (ProtoType msg) => AgentClient -> TransportSession msg -> ClientVar msg -> AM (Client msg)
|
||||||
waitForProtocolClient c (_, srv, _) v = do
|
waitForProtocolClient c (_, srv, _) v = do
|
||||||
NetworkConfig {tcpConnectTimeout} <- atomically $ getNetworkConfig c
|
NetworkConfig {tcpConnectTimeout, tcpTimeout} <- atomically $ getNetworkConfig c
|
||||||
client_ <- liftIO $ tcpConnectTimeout `timeout` atomically (readTMVar $ sessionVar v)
|
client_ <- liftIO $ (tcpConnectTimeout + tcpTimeout) `timeout` atomically (readTMVar $ sessionVar v)
|
||||||
liftEither $ case client_ of
|
liftEither $ case client_ of
|
||||||
Just (Right smpClient) -> Right smpClient
|
Just (Right smpClient) -> Right smpClient
|
||||||
Just (Left e) -> Left e
|
Just (Left e) -> Left e
|
||||||
@@ -1008,7 +1007,7 @@ runXFTPServerTest c userId (ProtoServerWithAuth srv auth) = do
|
|||||||
rcvPath <- getTempFilePath workDir
|
rcvPath <- getTempFilePath workDir
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
let tSess = (userId, srv, Nothing)
|
let tSess = (userId, srv, Nothing)
|
||||||
X.getXFTPClient g tSess cfg {xftpNetworkConfig} (\_ -> pure ()) >>= \case
|
X.getXFTPClient tSess cfg {xftpNetworkConfig} (\_ -> pure ()) >>= \case
|
||||||
Right xftp -> withTestChunk filePath $ do
|
Right xftp -> withTestChunk filePath $ do
|
||||||
(sndKey, spKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
(sndKey, spKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||||
(rcvKey, rpKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
(rcvKey, rpKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ import Control.Concurrent.Async
|
|||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import Control.Exception
|
import Control.Exception
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class (liftIO)
|
|
||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Control.Monad.Trans.Except
|
import Control.Monad.Trans.Except
|
||||||
import Crypto.Random (ChaChaDRG)
|
import Crypto.Random (ChaChaDRG)
|
||||||
import qualified Data.Aeson.TH as J
|
import qualified Data.Aeson.TH as J
|
||||||
@@ -107,19 +107,20 @@ import Simplex.Messaging.Protocol
|
|||||||
import Simplex.Messaging.TMap (TMap)
|
import Simplex.Messaging.TMap (TMap)
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport
|
import Simplex.Messaging.Transport
|
||||||
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), runTransportClient)
|
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTransportClient)
|
||||||
import Simplex.Messaging.Transport.KeepAlive
|
import Simplex.Messaging.Transport.KeepAlive
|
||||||
import Simplex.Messaging.Transport.WebSockets (WS)
|
import Simplex.Messaging.Transport.WebSockets (WS)
|
||||||
import Simplex.Messaging.Util (bshow, raceAny_, threadDelay')
|
import Simplex.Messaging.Util (bshow, raceAny_, threadDelay', whenM)
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
import System.Timeout (timeout)
|
import System.Timeout (timeout)
|
||||||
|
import UnliftIO (pooledMapConcurrentlyN)
|
||||||
|
|
||||||
-- | 'SMPClient' is a handle used to send commands to a specific SMP server.
|
-- | 'SMPClient' is a handle used to send commands to a specific SMP server.
|
||||||
--
|
--
|
||||||
-- Use 'getSMPClient' to connect to an SMP server and create a client handle.
|
-- Use 'getSMPClient' to connect to an SMP server and create a client handle.
|
||||||
data ProtocolClient v err msg = ProtocolClient
|
data ProtocolClient v err msg = ProtocolClient
|
||||||
{ action :: Maybe (Async ()),
|
{ action :: Maybe (Async ()),
|
||||||
thParams :: THandleParams v,
|
thParams :: THandleParams v 'TClient,
|
||||||
sessionTs :: UTCTime,
|
sessionTs :: UTCTime,
|
||||||
client_ :: PClient v err msg
|
client_ :: PClient v err msg
|
||||||
}
|
}
|
||||||
@@ -129,16 +130,16 @@ data PClient v err msg = PClient
|
|||||||
transportSession :: TransportSession msg,
|
transportSession :: TransportSession msg,
|
||||||
transportHost :: TransportHost,
|
transportHost :: TransportHost,
|
||||||
tcpTimeout :: Int,
|
tcpTimeout :: Int,
|
||||||
batchDelay :: Maybe Int,
|
rcvConcurrency :: Int,
|
||||||
pingErrorCount :: TVar Int,
|
pingErrorCount :: TVar Int,
|
||||||
clientCorrId :: TVar ChaChaDRG,
|
clientCorrId :: TVar ChaChaDRG,
|
||||||
sentCommands :: TMap CorrId (Request err msg),
|
sentCommands :: TMap CorrId (Request err msg),
|
||||||
sndQ :: TBQueue ByteString,
|
sndQ :: TBQueue (TVar Bool, ByteString),
|
||||||
rcvQ :: TBQueue (NonEmpty (SignedTransmission err msg)),
|
rcvQ :: TBQueue (NonEmpty (SignedTransmission err msg)),
|
||||||
msgQ :: Maybe (TBQueue (ServerTransmission v msg))
|
msgQ :: Maybe (TBQueue (ServerTransmission v msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
smpClientStub :: TVar ChaChaDRG -> ByteString -> VersionSMP -> Maybe THandleAuth -> STM SMPClient
|
smpClientStub :: TVar ChaChaDRG -> ByteString -> VersionSMP -> Maybe (THandleAuth 'TClient) -> STM SMPClient
|
||||||
smpClientStub g sessionId thVersion thAuth = do
|
smpClientStub g sessionId thVersion thAuth = do
|
||||||
connected <- newTVar False
|
connected <- newTVar False
|
||||||
clientCorrId <- C.newRandomDRG g
|
clientCorrId <- C.newRandomDRG g
|
||||||
@@ -165,7 +166,7 @@ smpClientStub g sessionId thVersion thAuth = do
|
|||||||
transportSession = (1, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:5001", Nothing),
|
transportSession = (1, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:5001", Nothing),
|
||||||
transportHost = "localhost",
|
transportHost = "localhost",
|
||||||
tcpTimeout = 15_000_000,
|
tcpTimeout = 15_000_000,
|
||||||
batchDelay = Nothing,
|
rcvConcurrency = 8,
|
||||||
pingErrorCount,
|
pingErrorCount,
|
||||||
clientCorrId,
|
clientCorrId,
|
||||||
sentCommands,
|
sentCommands,
|
||||||
@@ -208,6 +209,8 @@ data NetworkConfig = NetworkConfig
|
|||||||
tcpTimeout :: Int,
|
tcpTimeout :: Int,
|
||||||
-- | additional timeout per kilobyte (1024 bytes) to be sent
|
-- | additional timeout per kilobyte (1024 bytes) to be sent
|
||||||
tcpTimeoutPerKb :: Int64,
|
tcpTimeoutPerKb :: Int64,
|
||||||
|
-- | break response timeouts into groups, so later responses get later deadlines
|
||||||
|
rcvConcurrency :: Int,
|
||||||
-- | TCP keep-alive options, Nothing to skip enabling keep-alive
|
-- | TCP keep-alive options, Nothing to skip enabling keep-alive
|
||||||
tcpKeepAlive :: Maybe KeepAliveOpts,
|
tcpKeepAlive :: Maybe KeepAliveOpts,
|
||||||
-- | period for SMP ping commands (microseconds, 0 to disable)
|
-- | period for SMP ping commands (microseconds, 0 to disable)
|
||||||
@@ -228,9 +231,10 @@ defaultNetworkConfig =
|
|||||||
hostMode = HMOnionViaSocks,
|
hostMode = HMOnionViaSocks,
|
||||||
requiredHostMode = False,
|
requiredHostMode = False,
|
||||||
sessionMode = TSMUser,
|
sessionMode = TSMUser,
|
||||||
tcpConnectTimeout = 20_000_000,
|
tcpConnectTimeout = defaultTcpConnectTimeout,
|
||||||
tcpTimeout = 15_000_000,
|
tcpTimeout = 15_000_000,
|
||||||
tcpTimeoutPerKb = 5_000,
|
tcpTimeoutPerKb = 5_000,
|
||||||
|
rcvConcurrency = 8,
|
||||||
tcpKeepAlive = Just defaultKeepAliveOpts,
|
tcpKeepAlive = Just defaultKeepAliveOpts,
|
||||||
smpPingInterval = 600_000_000, -- 10min
|
smpPingInterval = 600_000_000, -- 10min
|
||||||
smpPingCount = 3,
|
smpPingCount = 3,
|
||||||
@@ -238,8 +242,8 @@ defaultNetworkConfig =
|
|||||||
}
|
}
|
||||||
|
|
||||||
transportClientConfig :: NetworkConfig -> TransportClientConfig
|
transportClientConfig :: NetworkConfig -> TransportClientConfig
|
||||||
transportClientConfig NetworkConfig {socksProxy, tcpKeepAlive, logTLSErrors} =
|
transportClientConfig NetworkConfig {socksProxy, tcpConnectTimeout, tcpKeepAlive, logTLSErrors} =
|
||||||
TransportClientConfig {socksProxy, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing}
|
TransportClientConfig {socksProxy, tcpConnectTimeout, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing}
|
||||||
{-# INLINE transportClientConfig #-}
|
{-# INLINE transportClientConfig #-}
|
||||||
|
|
||||||
-- | protocol client configuration.
|
-- | protocol client configuration.
|
||||||
@@ -252,8 +256,8 @@ data ProtocolClientConfig v = ProtocolClientConfig
|
|||||||
networkConfig :: NetworkConfig,
|
networkConfig :: NetworkConfig,
|
||||||
-- | client-server protocol version range
|
-- | client-server protocol version range
|
||||||
serverVRange :: VersionRange v,
|
serverVRange :: VersionRange v,
|
||||||
-- | delay between sending batches of commands (microseconds)
|
-- | agree shared session secret (used in SMP proxy)
|
||||||
batchDelay :: Maybe Int
|
agreeSecret :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Default protocol client configuration.
|
-- | Default protocol client configuration.
|
||||||
@@ -264,7 +268,7 @@ defaultClientConfig serverVRange =
|
|||||||
defaultTransport = ("443", transport @TLS),
|
defaultTransport = ("443", transport @TLS),
|
||||||
networkConfig = defaultNetworkConfig,
|
networkConfig = defaultNetworkConfig,
|
||||||
serverVRange,
|
serverVRange,
|
||||||
batchDelay = Nothing
|
agreeSecret = False
|
||||||
}
|
}
|
||||||
{-# INLINE defaultClientConfig #-}
|
{-# INLINE defaultClientConfig #-}
|
||||||
|
|
||||||
@@ -273,7 +277,8 @@ defaultSMPClientConfig = defaultClientConfig supportedClientSMPRelayVRange
|
|||||||
{-# INLINE defaultSMPClientConfig #-}
|
{-# INLINE defaultSMPClientConfig #-}
|
||||||
|
|
||||||
data Request err msg = Request
|
data Request err msg = Request
|
||||||
{ entityId :: EntityId,
|
{ corrId :: CorrId,
|
||||||
|
entityId :: EntityId,
|
||||||
responseVar :: TMVar (Either (ProtocolClientError err) msg)
|
responseVar :: TMVar (Either (ProtocolClientError err) msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,14 +326,14 @@ type TransportSession msg = (UserId, ProtoServer msg, Maybe EntityId)
|
|||||||
-- A single queue can be used for multiple 'SMPClient' instances,
|
-- A single queue can be used for multiple 'SMPClient' instances,
|
||||||
-- as 'SMPServerTransmission' includes server information.
|
-- as 'SMPServerTransmission' includes server information.
|
||||||
getProtocolClient :: forall v err msg. Protocol v err msg => TVar ChaChaDRG -> TransportSession msg -> ProtocolClientConfig v -> Maybe (TBQueue (ServerTransmission v msg)) -> (ProtocolClient v err msg -> IO ()) -> IO (Either (ProtocolClientError err) (ProtocolClient v err msg))
|
getProtocolClient :: forall v err msg. Protocol v err msg => TVar ChaChaDRG -> TransportSession msg -> ProtocolClientConfig v -> Maybe (TBQueue (ServerTransmission v msg)) -> (ProtocolClient v err msg -> IO ()) -> IO (Either (ProtocolClientError err) (ProtocolClient v err msg))
|
||||||
getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize, networkConfig, serverVRange, batchDelay} msgQ disconnected = do
|
getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize, networkConfig, serverVRange, agreeSecret} msgQ disconnected = do
|
||||||
case chooseTransportHost networkConfig (host srv) of
|
case chooseTransportHost networkConfig (host srv) of
|
||||||
Right useHost ->
|
Right useHost ->
|
||||||
(atomically (mkProtocolClient useHost) >>= runClient useTransport useHost)
|
(atomically (mkProtocolClient useHost) >>= runClient useTransport useHost)
|
||||||
`catch` \(e :: IOException) -> pure . Left $ PCEIOError e
|
`catch` \(e :: IOException) -> pure . Left $ PCEIOError e
|
||||||
Left e -> pure $ Left e
|
Left e -> pure $ Left e
|
||||||
where
|
where
|
||||||
NetworkConfig {tcpConnectTimeout, tcpTimeout, smpPingInterval} = networkConfig
|
NetworkConfig {tcpConnectTimeout, tcpTimeout, rcvConcurrency, smpPingInterval} = networkConfig
|
||||||
mkProtocolClient :: TransportHost -> STM (PClient v err msg)
|
mkProtocolClient :: TransportHost -> STM (PClient v err msg)
|
||||||
mkProtocolClient transportHost = do
|
mkProtocolClient transportHost = do
|
||||||
connected <- newTVar False
|
connected <- newTVar False
|
||||||
@@ -343,10 +348,10 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
|
|||||||
transportSession,
|
transportSession,
|
||||||
transportHost,
|
transportHost,
|
||||||
tcpTimeout,
|
tcpTimeout,
|
||||||
batchDelay,
|
|
||||||
pingErrorCount,
|
pingErrorCount,
|
||||||
clientCorrId,
|
clientCorrId,
|
||||||
sentCommands,
|
sentCommands,
|
||||||
|
rcvConcurrency,
|
||||||
sndQ,
|
sndQ,
|
||||||
rcvQ,
|
rcvQ,
|
||||||
msgQ
|
msgQ
|
||||||
@@ -355,13 +360,17 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
|
|||||||
runClient :: (ServiceName, ATransport) -> TransportHost -> PClient v err msg -> IO (Either (ProtocolClientError err) (ProtocolClient v err msg))
|
runClient :: (ServiceName, ATransport) -> TransportHost -> PClient v err msg -> IO (Either (ProtocolClientError err) (ProtocolClient v err msg))
|
||||||
runClient (port', ATransport t) useHost c = do
|
runClient (port', ATransport t) useHost c = do
|
||||||
cVar <- newEmptyTMVarIO
|
cVar <- newEmptyTMVarIO
|
||||||
let tcConfig = transportClientConfig networkConfig
|
let (connectTimeout, tcConfig) = case transportClientConfig networkConfig of
|
||||||
|
tcc@TransportClientConfig {socksProxy = Nothing} -> (tcpConnectTimeout, tcc)
|
||||||
|
tcc@TransportClientConfig {socksProxy = Just _} ->
|
||||||
|
let extended = tcpConnectTimeout + tcpTimeout -- typically, extra time for establishing TOR circuits
|
||||||
|
in (extended, tcc {tcpConnectTimeout = extended} :: TransportClientConfig)
|
||||||
username = proxyUsername transportSession
|
username = proxyUsername transportSession
|
||||||
action <-
|
action <-
|
||||||
async $
|
async $
|
||||||
runTransportClient tcConfig (Just username) useHost port' (Just $ keyHash srv) (client t c cVar)
|
runTransportClient tcConfig (Just username) useHost port' (Just $ keyHash srv) (client t c cVar)
|
||||||
`finally` atomically (tryPutTMVar cVar $ Left PCENetworkError)
|
`finally` atomically (tryPutTMVar cVar $ Left PCENetworkError)
|
||||||
c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar)
|
c_ <- (connectTimeout + tcpTimeout) `timeout` atomically (takeTMVar cVar)
|
||||||
case c_ of
|
case c_ of
|
||||||
Just (Right c') -> pure $ Right c' {action = Just action}
|
Just (Right c') -> pure $ Right c' {action = Just action}
|
||||||
Just (Left e) -> pure $ Left e
|
Just (Left e) -> pure $ Left e
|
||||||
@@ -375,7 +384,7 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
|
|||||||
|
|
||||||
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 -> PClient v err msg -> TMVar (Either (ProtocolClientError err) (ProtocolClient v err msg)) -> c -> IO ()
|
||||||
client _ c cVar h = do
|
client _ c cVar h = do
|
||||||
ks <- atomically $ C.generateKeyPair g
|
ks <- if agreeSecret then Just <$> atomically (C.generateKeyPair g) else pure Nothing
|
||||||
runExceptT (protocolClientHandshake @v @err @msg h ks (keyHash srv) serverVRange) >>= \case
|
runExceptT (protocolClientHandshake @v @err @msg h ks (keyHash srv) serverVRange) >>= \case
|
||||||
Left e -> atomically . putTMVar cVar . Left $ PCETransportError e
|
Left e -> atomically . putTMVar cVar . Left $ PCETransportError e
|
||||||
Right th@THandle {params} -> do
|
Right th@THandle {params} -> do
|
||||||
@@ -387,10 +396,10 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
|
|||||||
raceAny_ ([send c' th, process c', receive c' th] <> [ping c' | smpPingInterval > 0])
|
raceAny_ ([send c' th, process c', receive c' th] <> [ping c' | smpPingInterval > 0])
|
||||||
`finally` disconnected c'
|
`finally` disconnected c'
|
||||||
|
|
||||||
send :: Transport c => ProtocolClient v err msg -> THandle v c -> IO ()
|
send :: Transport c => ProtocolClient v err msg -> THandle v c 'TClient -> IO ()
|
||||||
send ProtocolClient {client_ = PClient {sndQ}} h = forever $ atomically (readTBQueue sndQ) >>= tPutLog h
|
send ProtocolClient {client_ = PClient {sndQ}} h = forever $ atomically (readTBQueue sndQ) >>= \(active, s) -> whenM (readTVarIO active) (void $ tPutLog h s)
|
||||||
|
|
||||||
receive :: Transport c => ProtocolClient v err msg -> THandle v c -> IO ()
|
receive :: Transport c => ProtocolClient v err msg -> THandle v c 'TClient -> IO ()
|
||||||
receive ProtocolClient {client_ = PClient {rcvQ}} h = forever $ tGet h >>= atomically . writeTBQueue rcvQ
|
receive ProtocolClient {client_ = PClient {rcvQ}} h = forever $ tGet h >>= atomically . writeTBQueue rcvQ
|
||||||
|
|
||||||
ping :: ProtocolClient v err msg -> IO ()
|
ping :: ProtocolClient v err msg -> IO ()
|
||||||
@@ -677,19 +686,21 @@ streamProtocolCommands c@ProtocolClient {thParams = THandleParams {batch, blockS
|
|||||||
mapM_ (cb <=< sendBatch c) bs
|
mapM_ (cb <=< sendBatch c) bs
|
||||||
|
|
||||||
sendBatch :: ProtocolClient v err msg -> TransportBatch (Request err msg) -> IO [Response err msg]
|
sendBatch :: ProtocolClient v err msg -> TransportBatch (Request err msg) -> IO [Response err msg]
|
||||||
sendBatch c@ProtocolClient {client_ = PClient {sndQ}} b = do
|
sendBatch c@ProtocolClient {client_ = PClient {rcvConcurrency, sndQ}} b = do
|
||||||
case b of
|
case b of
|
||||||
TBError e Request {entityId} -> do
|
TBError e Request {entityId} -> do
|
||||||
putStrLn "send error: large message"
|
putStrLn "send error: large message"
|
||||||
pure [Response entityId $ Left $ PCETransportError e]
|
pure [Response entityId $ Left $ PCETransportError e]
|
||||||
TBTransmissions s n rs
|
TBTransmissions s n rs
|
||||||
| n > 0 -> do
|
| n > 0 -> do
|
||||||
atomically $ writeTBQueue sndQ s
|
active <- newTVarIO True
|
||||||
mapConcurrently (getResponse c) rs
|
atomically $ writeTBQueue sndQ (active, s)
|
||||||
|
pooledMapConcurrentlyN rcvConcurrency (getResponse c active) rs
|
||||||
| otherwise -> pure []
|
| otherwise -> pure []
|
||||||
TBTransmission s r -> do
|
TBTransmission s r -> do
|
||||||
atomically $ writeTBQueue sndQ s
|
active <- newTVarIO True
|
||||||
(: []) <$> getResponse c r
|
atomically $ writeTBQueue sndQ (active, s)
|
||||||
|
(: []) <$> getResponse c active r
|
||||||
|
|
||||||
-- | Send Protocol command
|
-- | Send Protocol command
|
||||||
sendProtocolCommand :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.APrivateAuthKey -> EntityId -> ProtoCommand msg -> ExceptT (ProtocolClientError err) IO msg
|
sendProtocolCommand :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.APrivateAuthKey -> EntityId -> ProtoCommand msg -> ExceptT (ProtocolClientError err) IO msg
|
||||||
@@ -702,19 +713,22 @@ sendProtocolCommand c@ProtocolClient {client_ = PClient {sndQ}, thParams = THand
|
|||||||
Left e -> pure . Left $ PCETransportError e
|
Left e -> pure . Left $ PCETransportError e
|
||||||
Right t
|
Right t
|
||||||
| B.length s > blockSize - 2 -> pure . Left $ PCETransportError TELargeMsg
|
| B.length s > blockSize - 2 -> pure . Left $ PCETransportError TELargeMsg
|
||||||
| otherwise -> atomically (writeTBQueue sndQ s) >> response <$> getResponse c r
|
| otherwise -> do
|
||||||
|
active <- newTVarIO True
|
||||||
|
atomically (writeTBQueue sndQ (active, s))
|
||||||
|
response <$> getResponse c active r
|
||||||
where
|
where
|
||||||
s
|
s
|
||||||
| batch = tEncodeBatch1 t
|
| batch = tEncodeBatch1 t
|
||||||
| otherwise = tEncode t
|
| otherwise = tEncode t
|
||||||
|
|
||||||
-- TODO switch to timeout or TimeManager that supports Int64
|
-- TODO switch to timeout or TimeManager that supports Int64
|
||||||
getResponse :: ProtocolClient v err msg -> Request err msg -> IO (Response err msg)
|
getResponse :: ProtocolClient v err msg -> TVar Bool -> Request err msg -> IO (Response err msg)
|
||||||
getResponse ProtocolClient {client_ = PClient {tcpTimeout, pingErrorCount}} Request {entityId, responseVar} = do
|
getResponse ProtocolClient {client_ = PClient {tcpTimeout, pingErrorCount, sentCommands}} active Request {corrId, entityId, responseVar} = do
|
||||||
response <-
|
response <-
|
||||||
timeout tcpTimeout (atomically (takeTMVar responseVar)) >>= \case
|
timeout tcpTimeout (atomically (takeTMVar responseVar)) >>= \case
|
||||||
Just r -> atomically (writeTVar pingErrorCount 0) $> r
|
Just r -> atomically (writeTVar pingErrorCount 0) $> r
|
||||||
Nothing -> pure $ Left PCEResponseTimeout
|
Nothing -> atomically (writeTVar active False >> TM.delete corrId sentCommands) $> Left PCEResponseTimeout
|
||||||
pure Response {entityId, response}
|
pure Response {entityId, response}
|
||||||
|
|
||||||
mkTransmission :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> ClientCommand msg -> IO (PCTransmission err msg)
|
mkTransmission :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> ClientCommand msg -> IO (PCTransmission err msg)
|
||||||
@@ -729,17 +743,17 @@ mkTransmission ProtocolClient {thParams, client_ = PClient {clientCorrId, sentCo
|
|||||||
getNextCorrId = CorrId <$> C.randomBytes 24 clientCorrId -- also used as nonce
|
getNextCorrId = CorrId <$> C.randomBytes 24 clientCorrId -- also used as nonce
|
||||||
mkRequest :: CorrId -> STM (Request err msg)
|
mkRequest :: CorrId -> STM (Request err msg)
|
||||||
mkRequest corrId = do
|
mkRequest corrId = do
|
||||||
r <- Request entId <$> newEmptyTMVar
|
r <- Request corrId entId <$> newEmptyTMVar
|
||||||
TM.insert corrId r sentCommands
|
TM.insert corrId r sentCommands
|
||||||
pure r
|
pure r
|
||||||
|
|
||||||
authTransmission :: Maybe THandleAuth -> Maybe C.APrivateAuthKey -> CorrId -> ByteString -> Either TransportError (Maybe TransmissionAuth)
|
authTransmission :: Maybe (THandleAuth 'TClient) -> Maybe C.APrivateAuthKey -> CorrId -> ByteString -> Either TransportError (Maybe TransmissionAuth)
|
||||||
authTransmission thAuth pKey_ (CorrId corrId) t = traverse authenticate pKey_
|
authTransmission thAuth pKey_ (CorrId corrId) t = traverse authenticate pKey_
|
||||||
where
|
where
|
||||||
authenticate :: C.APrivateAuthKey -> Either TransportError TransmissionAuth
|
authenticate :: C.APrivateAuthKey -> Either TransportError TransmissionAuth
|
||||||
authenticate (C.APrivateAuthKey a pk) = case a of
|
authenticate (C.APrivateAuthKey a pk) = case a of
|
||||||
C.SX25519 -> case thAuth of
|
C.SX25519 -> case thAuth of
|
||||||
Just THandleAuth {peerPubKey} -> Right $ TAAuthenticator $ C.cbAuthenticate peerPubKey pk (C.cbNonce corrId) t
|
Just THAuthClient {serverPeerPubKey = k} -> Right $ TAAuthenticator $ C.cbAuthenticate k pk (C.cbNonce corrId) t
|
||||||
Nothing -> Left TENoServerAuth
|
Nothing -> Left TENoServerAuth
|
||||||
C.SEd25519 -> sign pk
|
C.SEd25519 -> sign pk
|
||||||
C.SEd448 -> sign pk
|
C.SEd448 -> sign pk
|
||||||
|
|||||||
@@ -163,8 +163,8 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
|
|||||||
|
|
||||||
waitForSMPClient :: SMPClientVar -> ExceptT SMPClientError IO SMPClient
|
waitForSMPClient :: SMPClientVar -> ExceptT SMPClientError IO SMPClient
|
||||||
waitForSMPClient v = do
|
waitForSMPClient v = do
|
||||||
let ProtocolClientConfig {networkConfig = NetworkConfig {tcpConnectTimeout}} = smpCfg agentCfg
|
let ProtocolClientConfig {networkConfig = NetworkConfig {tcpConnectTimeout, tcpTimeout}} = smpCfg agentCfg
|
||||||
smpClient_ <- liftIO $ tcpConnectTimeout `timeout` atomically (readTMVar $ sessionVar v)
|
smpClient_ <- liftIO $ (tcpConnectTimeout + tcpTimeout) `timeout` atomically (readTMVar $ sessionVar v)
|
||||||
liftEither $ case smpClient_ of
|
liftEither $ case smpClient_ of
|
||||||
Just (Right smpClient) -> Right smpClient
|
Just (Right smpClient) -> Right smpClient
|
||||||
Just (Left e) -> Left e
|
Just (Left e) -> Left e
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ instance Encoding ANewNtfEntity where
|
|||||||
instance Protocol NTFVersion ErrorType NtfResponse where
|
instance Protocol NTFVersion ErrorType NtfResponse where
|
||||||
type ProtoCommand NtfResponse = NtfCmd
|
type ProtoCommand NtfResponse = NtfCmd
|
||||||
type ProtoType NtfResponse = 'PNTF
|
type ProtoType NtfResponse = 'PNTF
|
||||||
protocolClientHandshake = ntfClientHandshake
|
protocolClientHandshake c _ks = ntfClientHandshake c
|
||||||
protocolPing = NtfCmd SSubscription PING
|
protocolPing = NtfCmd SSubscription PING
|
||||||
protocolError = \case
|
protocolError = \case
|
||||||
NRErr e -> Just e
|
NRErr e -> Just e
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import qualified Simplex.Messaging.Protocol as SMP
|
|||||||
import Simplex.Messaging.Server
|
import Simplex.Messaging.Server
|
||||||
import Simplex.Messaging.Server.Stats
|
import Simplex.Messaging.Server.Stats
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport (ATransport (..), THandle (..), THandleAuth (..), THandleParams (..), TProxy, Transport (..))
|
import Simplex.Messaging.Transport (ATransport (..), THandle (..), THandleAuth (..), THandleParams (..), TProxy, Transport (..), TransportPeer (..))
|
||||||
import Simplex.Messaging.Transport.Server (runTransportServer, tlsServerCredentials)
|
import Simplex.Messaging.Transport.Server (runTransportServer, tlsServerCredentials)
|
||||||
import Simplex.Messaging.Util
|
import Simplex.Messaging.Util
|
||||||
import System.Exit (exitFailure)
|
import System.Exit (exitFailure)
|
||||||
@@ -339,7 +339,7 @@ updateTknStatus NtfTknData {ntfTknId, tknStatus} status = do
|
|||||||
old <- atomically $ stateTVar tknStatus (,status)
|
old <- atomically $ stateTVar tknStatus (,status)
|
||||||
when (old /= status) $ withNtfLog $ \sl -> logTokenStatus sl ntfTknId status
|
when (old /= status) $ withNtfLog $ \sl -> logTokenStatus sl ntfTknId status
|
||||||
|
|
||||||
runNtfClientTransport :: Transport c => THandleNTF c -> M ()
|
runNtfClientTransport :: Transport c => THandleNTF c 'TServer -> M ()
|
||||||
runNtfClientTransport th@THandle {params} = do
|
runNtfClientTransport th@THandle {params} = do
|
||||||
qSize <- asks $ clientQSize . config
|
qSize <- asks $ clientQSize . config
|
||||||
ts <- liftIO getSystemTime
|
ts <- liftIO getSystemTime
|
||||||
@@ -356,7 +356,7 @@ runNtfClientTransport th@THandle {params} = do
|
|||||||
clientDisconnected :: NtfServerClient -> IO ()
|
clientDisconnected :: NtfServerClient -> IO ()
|
||||||
clientDisconnected NtfServerClient {connected} = atomically $ writeTVar connected False
|
clientDisconnected NtfServerClient {connected} = atomically $ writeTVar connected False
|
||||||
|
|
||||||
receive :: Transport c => THandleNTF c -> NtfServerClient -> M ()
|
receive :: Transport c => THandleNTF c 'TServer -> NtfServerClient -> M ()
|
||||||
receive th@THandle {params = THandleParams {thAuth}} NtfServerClient {rcvQ, sndQ, rcvActiveAt} = forever $ do
|
receive th@THandle {params = THandleParams {thAuth}} NtfServerClient {rcvQ, sndQ, rcvActiveAt} = forever $ do
|
||||||
ts <- liftIO $ tGet th
|
ts <- liftIO $ tGet th
|
||||||
forM_ ts $ \t@(_, _, (corrId, entId, cmdOrError)) -> do
|
forM_ ts $ \t@(_, _, (corrId, entId, cmdOrError)) -> do
|
||||||
@@ -371,7 +371,7 @@ receive th@THandle {params = THandleParams {thAuth}} NtfServerClient {rcvQ, sndQ
|
|||||||
where
|
where
|
||||||
write q t = atomically $ writeTBQueue q t
|
write q t = atomically $ writeTBQueue q t
|
||||||
|
|
||||||
send :: Transport c => THandleNTF c -> NtfServerClient -> IO ()
|
send :: Transport c => THandleNTF c 'TServer -> NtfServerClient -> IO ()
|
||||||
send h@THandle {params} NtfServerClient {sndQ, sndActiveAt} = forever $ do
|
send h@THandle {params} NtfServerClient {sndQ, sndActiveAt} = forever $ do
|
||||||
t <- atomically $ readTBQueue sndQ
|
t <- atomically $ readTBQueue sndQ
|
||||||
void . liftIO $ tPut h [Right (Nothing, encodeTransmission params t)]
|
void . liftIO $ tPut h [Right (Nothing, encodeTransmission params t)]
|
||||||
@@ -382,7 +382,7 @@ send h@THandle {params} NtfServerClient {sndQ, sndActiveAt} = forever $ do
|
|||||||
|
|
||||||
data VerificationResult = VRVerified NtfRequest | VRFailed
|
data VerificationResult = VRVerified NtfRequest | VRFailed
|
||||||
|
|
||||||
verifyNtfTransmission :: Maybe (THandleAuth, C.CbNonce) -> SignedTransmission ErrorType NtfCmd -> NtfCmd -> M VerificationResult
|
verifyNtfTransmission :: Maybe (THandleAuth 'TServer, C.CbNonce) -> SignedTransmission ErrorType NtfCmd -> NtfCmd -> M VerificationResult
|
||||||
verifyNtfTransmission auth_ (tAuth, authorized, (corrId, entId, _)) cmd = do
|
verifyNtfTransmission auth_ (tAuth, authorized, (corrId, entId, _)) cmd = do
|
||||||
st <- asks store
|
st <- asks store
|
||||||
case cmd of
|
case cmd of
|
||||||
|
|||||||
@@ -24,16 +24,16 @@ import Numeric.Natural
|
|||||||
import Simplex.Messaging.Client.Agent
|
import Simplex.Messaging.Client.Agent
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Notifications.Protocol
|
import Simplex.Messaging.Notifications.Protocol
|
||||||
import Simplex.Messaging.Notifications.Transport (NTFVersion, VersionRangeNTF)
|
|
||||||
import Simplex.Messaging.Notifications.Server.Push.APNS
|
import Simplex.Messaging.Notifications.Server.Push.APNS
|
||||||
import Simplex.Messaging.Notifications.Server.Stats
|
import Simplex.Messaging.Notifications.Server.Stats
|
||||||
import Simplex.Messaging.Notifications.Server.Store
|
import Simplex.Messaging.Notifications.Server.Store
|
||||||
import Simplex.Messaging.Notifications.Server.StoreLog
|
import Simplex.Messaging.Notifications.Server.StoreLog
|
||||||
|
import Simplex.Messaging.Notifications.Transport (NTFVersion, VersionRangeNTF)
|
||||||
import Simplex.Messaging.Protocol (CorrId, SMPServer, Transmission)
|
import Simplex.Messaging.Protocol (CorrId, SMPServer, Transmission)
|
||||||
import Simplex.Messaging.Server.Expiration
|
import Simplex.Messaging.Server.Expiration
|
||||||
import Simplex.Messaging.TMap (TMap)
|
import Simplex.Messaging.TMap (TMap)
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport (ATransport, THandleParams)
|
import Simplex.Messaging.Transport (ATransport, THandleParams, TransportPeer (..))
|
||||||
import Simplex.Messaging.Transport.Server (TransportServerConfig, loadFingerprint, loadTLSServerParams)
|
import Simplex.Messaging.Transport.Server (TransportServerConfig, loadFingerprint, loadTLSServerParams)
|
||||||
import System.IO (IOMode (..))
|
import System.IO (IOMode (..))
|
||||||
import System.Mem.Weak (Weak)
|
import System.Mem.Weak (Weak)
|
||||||
@@ -161,13 +161,13 @@ data NtfRequest
|
|||||||
data NtfServerClient = NtfServerClient
|
data NtfServerClient = NtfServerClient
|
||||||
{ rcvQ :: TBQueue NtfRequest,
|
{ rcvQ :: TBQueue NtfRequest,
|
||||||
sndQ :: TBQueue (Transmission NtfResponse),
|
sndQ :: TBQueue (Transmission NtfResponse),
|
||||||
ntfThParams :: THandleParams NTFVersion,
|
ntfThParams :: THandleParams NTFVersion 'TServer,
|
||||||
connected :: TVar Bool,
|
connected :: TVar Bool,
|
||||||
rcvActiveAt :: TVar SystemTime,
|
rcvActiveAt :: TVar SystemTime,
|
||||||
sndActiveAt :: TVar SystemTime
|
sndActiveAt :: TVar SystemTime
|
||||||
}
|
}
|
||||||
|
|
||||||
newNtfServerClient :: Natural -> THandleParams NTFVersion -> SystemTime -> STM NtfServerClient
|
newNtfServerClient :: Natural -> THandleParams NTFVersion 'TServer -> SystemTime -> STM NtfServerClient
|
||||||
newNtfServerClient qSize ntfThParams ts = do
|
newNtfServerClient qSize ntfThParams ts = do
|
||||||
rcvQ <- newTBQueue qSize
|
rcvQ <- newTBQueue qSize
|
||||||
sndQ <- newTBQueue qSize
|
sndQ <- newTBQueue qSize
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import Data.Maybe (fromMaybe)
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Network.Socket (HostName)
|
import Network.Socket (HostName)
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
import Simplex.Messaging.Client (ProtocolClientConfig (..))
|
import Simplex.Messaging.Client.Agent (defaultSMPClientAgentConfig)
|
||||||
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Notifications.Server (runNtfServer)
|
import Simplex.Messaging.Notifications.Server (runNtfServer)
|
||||||
import Simplex.Messaging.Notifications.Server.Env (NtfServerConfig (..), defaultInactiveClientExpiration)
|
import Simplex.Messaging.Notifications.Server.Env (NtfServerConfig (..), defaultInactiveClientExpiration)
|
||||||
@@ -31,9 +30,6 @@ import System.FilePath (combine)
|
|||||||
import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
|
import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
|
||||||
import Text.Read (readMaybe)
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
defaultSMPBatchDelay :: Int
|
|
||||||
defaultSMPBatchDelay = 10000
|
|
||||||
|
|
||||||
ntfServerCLI :: FilePath -> FilePath -> IO ()
|
ntfServerCLI :: FilePath -> FilePath -> IO ()
|
||||||
ntfServerCLI cfgPath logPath =
|
ntfServerCLI cfgPath logPath =
|
||||||
getCliCommand' (cliCommandP cfgPath logPath iniFile) serverVersion >>= \case
|
getCliCommand' (cliCommandP cfgPath logPath iniFile) serverVersion >>= \case
|
||||||
@@ -87,9 +83,7 @@ ntfServerCLI cfgPath logPath =
|
|||||||
\# host is only used to print server address on start\n"
|
\# host is only used to print server address on start\n"
|
||||||
<> ("host: " <> host <> "\n")
|
<> ("host: " <> host <> "\n")
|
||||||
<> ("port: " <> defaultServerPort <> "\n")
|
<> ("port: " <> defaultServerPort <> "\n")
|
||||||
<> "log_tls_errors: off\n\
|
<> "log_tls_errors: off\n"
|
||||||
\# delay between command batches sent to SMP relays (microseconds), 0 to disable\n"
|
|
||||||
<> ("smp_batch_delay: " <> show defaultSMPBatchDelay <> "\n")
|
|
||||||
<> "websockets: off\n\n\
|
<> "websockets: off\n\n\
|
||||||
\[INACTIVE_CLIENTS]\n\
|
\[INACTIVE_CLIENTS]\n\
|
||||||
\# TTL and interval to check inactive clients\n\
|
\# TTL and interval to check inactive clients\n\
|
||||||
@@ -111,8 +105,6 @@ ntfServerCLI cfgPath logPath =
|
|||||||
enableStoreLog = settingIsOn "STORE_LOG" "enable" ini
|
enableStoreLog = settingIsOn "STORE_LOG" "enable" ini
|
||||||
logStats = settingIsOn "STORE_LOG" "log_stats" ini
|
logStats = settingIsOn "STORE_LOG" "log_stats" ini
|
||||||
c = combine cfgPath . ($ defaultX509Config)
|
c = combine cfgPath . ($ defaultX509Config)
|
||||||
smpBatchDelay = readIniDefault defaultSMPBatchDelay "TRANSPORT" "smp_batch_delay" ini
|
|
||||||
batchDelay = if smpBatchDelay <= 0 then Nothing else Just smpBatchDelay
|
|
||||||
serverConfig =
|
serverConfig =
|
||||||
NtfServerConfig
|
NtfServerConfig
|
||||||
{ transports = iniTransports ini,
|
{ transports = iniTransports ini,
|
||||||
@@ -121,7 +113,7 @@ ntfServerCLI cfgPath logPath =
|
|||||||
clientQSize = 64,
|
clientQSize = 64,
|
||||||
subQSize = 512,
|
subQSize = 512,
|
||||||
pushQSize = 1048,
|
pushQSize = 1048,
|
||||||
smpAgentCfg = defaultSMPClientAgentConfig {smpCfg = (smpCfg defaultSMPClientAgentConfig) {batchDelay}},
|
smpAgentCfg = defaultSMPClientAgentConfig,
|
||||||
apnsConfig = defaultAPNSPushClientConfig,
|
apnsConfig = defaultAPNSPushClientConfig,
|
||||||
subsBatchSize = 900,
|
subsBatchSize = 900,
|
||||||
inactiveClientExpiration =
|
inactiveClientExpiration =
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE PatternSynonyms #-}
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
|
||||||
module Simplex.Messaging.Notifications.Transport where
|
module Simplex.Messaging.Notifications.Transport where
|
||||||
|
|
||||||
@@ -18,9 +19,9 @@ import qualified Data.X509 as X
|
|||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Transport
|
import Simplex.Messaging.Transport
|
||||||
|
import Simplex.Messaging.Util (liftEitherWith)
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
import Simplex.Messaging.Version.Internal
|
import Simplex.Messaging.Version.Internal
|
||||||
import Simplex.Messaging.Util (liftEitherWith)
|
|
||||||
|
|
||||||
ntfBlockSize :: Int
|
ntfBlockSize :: Int
|
||||||
ntfBlockSize = 512
|
ntfBlockSize = 512
|
||||||
@@ -54,7 +55,7 @@ supportedClientNTFVRange = mkVersionRange initialNTFVersion currentClientNTFVers
|
|||||||
supportedServerNTFVRange :: VersionRangeNTF
|
supportedServerNTFVRange :: VersionRangeNTF
|
||||||
supportedServerNTFVRange = mkVersionRange initialNTFVersion currentServerNTFVersion
|
supportedServerNTFVRange = mkVersionRange initialNTFVersion currentServerNTFVersion
|
||||||
|
|
||||||
type THandleNTF c = THandle NTFVersion c
|
type THandleNTF c p = THandle NTFVersion c p
|
||||||
|
|
||||||
data NtfServerHandshake = NtfServerHandshake
|
data NtfServerHandshake = NtfServerHandshake
|
||||||
{ ntfVersionRange :: VersionRangeNTF,
|
{ ntfVersionRange :: VersionRangeNTF,
|
||||||
@@ -67,9 +68,7 @@ data NtfClientHandshake = NtfClientHandshake
|
|||||||
{ -- | agreed SMP notifications server protocol version
|
{ -- | agreed SMP notifications server protocol version
|
||||||
ntfVersion :: VersionNTF,
|
ntfVersion :: VersionNTF,
|
||||||
-- | server identity - CA certificate fingerprint
|
-- | server identity - CA certificate fingerprint
|
||||||
keyHash :: C.KeyHash,
|
keyHash :: C.KeyHash
|
||||||
-- pub key to agree shared secret for entity ID encryption, shared secret for command authorization is agreed using per-queue keys.
|
|
||||||
authPubKey :: Maybe C.PublicKeyX25519
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instance Encoding NtfServerHandshake where
|
instance Encoding NtfServerHandshake where
|
||||||
@@ -94,62 +93,61 @@ authEncryptCmdsP :: VersionNTF -> Parser a -> Parser (Maybe a)
|
|||||||
authEncryptCmdsP v p = if v >= authBatchCmdsNTFVersion then Just <$> p else pure Nothing
|
authEncryptCmdsP v p = if v >= authBatchCmdsNTFVersion then Just <$> p else pure Nothing
|
||||||
|
|
||||||
instance Encoding NtfClientHandshake where
|
instance Encoding NtfClientHandshake where
|
||||||
smpEncode NtfClientHandshake {ntfVersion, keyHash, authPubKey} =
|
smpEncode NtfClientHandshake {ntfVersion, keyHash} =
|
||||||
smpEncode (ntfVersion, keyHash) <> encodeNtfAuthPubKey ntfVersion authPubKey
|
smpEncode (ntfVersion, keyHash)
|
||||||
smpP = do
|
smpP = do
|
||||||
(ntfVersion, keyHash) <- smpP
|
(ntfVersion, keyHash) <- smpP
|
||||||
-- TODO drop SMP v6: remove special parser and make key non-optional
|
pure NtfClientHandshake {ntfVersion, keyHash}
|
||||||
authPubKey <- ntfAuthPubKeyP ntfVersion
|
|
||||||
pure NtfClientHandshake {ntfVersion, keyHash, authPubKey}
|
|
||||||
|
|
||||||
ntfAuthPubKeyP :: VersionNTF -> Parser (Maybe C.PublicKeyX25519)
|
|
||||||
ntfAuthPubKeyP v = if v >= authBatchCmdsNTFVersion then Just <$> smpP else pure Nothing
|
|
||||||
|
|
||||||
encodeNtfAuthPubKey :: VersionNTF -> Maybe C.PublicKeyX25519 -> ByteString
|
|
||||||
encodeNtfAuthPubKey v k
|
|
||||||
| v >= authBatchCmdsNTFVersion = maybe "" smpEncode k
|
|
||||||
| otherwise = ""
|
|
||||||
|
|
||||||
-- | Notifcations server transport handshake.
|
-- | Notifcations server transport handshake.
|
||||||
ntfServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c)
|
ntfServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c 'TServer)
|
||||||
ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do
|
ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do
|
||||||
let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c
|
let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c
|
||||||
let sk = C.signX509 serverSignKey $ C.publicToX509 k
|
let sk = C.signX509 serverSignKey $ C.publicToX509 k
|
||||||
sendHandshake th $ NtfServerHandshake {sessionId, ntfVersionRange = ntfVRange, authPubKey = Just sk}
|
sendHandshake th $ NtfServerHandshake {sessionId, ntfVersionRange = ntfVRange, authPubKey = Just sk}
|
||||||
getHandshake th >>= \case
|
getHandshake th >>= \case
|
||||||
NtfClientHandshake {ntfVersion = v, keyHash, authPubKey = k'}
|
NtfClientHandshake {ntfVersion = v, keyHash}
|
||||||
| keyHash /= kh ->
|
| keyHash /= kh ->
|
||||||
throwError $ TEHandshake IDENTITY
|
throwError $ TEHandshake IDENTITY
|
||||||
| v `isCompatible` ntfVRange ->
|
| v `isCompatible` ntfVRange ->
|
||||||
pure $ ntfThHandle th v pk k'
|
pure $ ntfThHandleServer th v pk
|
||||||
| otherwise -> throwError $ TEHandshake VERSION
|
| otherwise -> throwError $ TEHandshake VERSION
|
||||||
|
|
||||||
-- | Notifcations server client transport handshake.
|
-- | Notifcations server client transport handshake.
|
||||||
ntfClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c)
|
ntfClientHandshake :: forall c. Transport c => c -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c 'TClient)
|
||||||
ntfClientHandshake c (k, pk) keyHash ntfVRange = do
|
ntfClientHandshake c keyHash ntfVRange = do
|
||||||
let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c
|
let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c
|
||||||
NtfServerHandshake {sessionId = sessId, ntfVersionRange, authPubKey = sk'} <- getHandshake th
|
NtfServerHandshake {sessionId = sessId, ntfVersionRange, authPubKey = sk'} <- getHandshake th
|
||||||
if sessionId /= sessId
|
if sessionId /= sessId
|
||||||
then throwError TEBadSession
|
then throwError TEBadSession
|
||||||
else case ntfVersionRange `compatibleVersion` ntfVRange of
|
else case ntfVersionRange `compatibleVersion` ntfVRange of
|
||||||
Just (Compatible v) -> do
|
Just (Compatible v) -> do
|
||||||
sk_ <- forM sk' $ \exact -> liftEitherWith (const $ TEHandshake BAD_AUTH) $ do
|
ck_ <- forM sk' $ \signedKey -> liftEitherWith (const $ TEHandshake BAD_AUTH) $ do
|
||||||
serverKey <- getServerVerifyKey c
|
serverKey <- getServerVerifyKey c
|
||||||
pubKey <- C.verifyX509 serverKey exact
|
pubKey <- C.verifyX509 serverKey signedKey
|
||||||
C.x509ToPublic (pubKey, []) >>= C.pubKey
|
(,(getServerCerts c, signedKey)) <$> (C.x509ToPublic (pubKey, []) >>= C.pubKey)
|
||||||
sendHandshake th $ NtfClientHandshake {ntfVersion = v, keyHash, authPubKey = Just k}
|
sendHandshake th $ NtfClientHandshake {ntfVersion = v, keyHash}
|
||||||
pure $ ntfThHandle th v pk sk_
|
pure $ ntfThHandleClient th v ck_
|
||||||
Nothing -> throwError $ TEHandshake VERSION
|
Nothing -> throwError $ TEHandshake VERSION
|
||||||
|
|
||||||
ntfThHandle :: forall c. THandleNTF c -> VersionNTF -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleNTF c
|
ntfThHandleServer :: forall c. THandleNTF c 'TServer -> VersionNTF -> C.PrivateKeyX25519 -> THandleNTF c 'TServer
|
||||||
ntfThHandle th@THandle {params} v privKey k_ =
|
ntfThHandleServer th v pk =
|
||||||
-- TODO drop SMP v6: make thAuth non-optional
|
let thAuth = THAuthServer {serverPrivKey = pk, sessSecret' = Nothing}
|
||||||
let thAuth = (\k -> THandleAuth {peerPubKey = k, privKey}) <$> k_
|
in ntfThHandle_ th v (Just thAuth)
|
||||||
v3 = v >= authBatchCmdsNTFVersion
|
|
||||||
params' = params {thVersion = v, thAuth, implySessId = v3, batch = v3}
|
|
||||||
in (th :: THandleNTF c) {params = params'}
|
|
||||||
|
|
||||||
ntfTHandle :: Transport c => c -> THandleNTF c
|
ntfThHandleClient :: forall c. THandleNTF c 'TClient -> VersionNTF -> Maybe (C.PublicKeyX25519, (X.CertificateChain, X.SignedExact X.PubKey)) -> THandleNTF c 'TClient
|
||||||
|
ntfThHandleClient th v ck_ =
|
||||||
|
let thAuth = (\(k, ck) -> THAuthClient {serverPeerPubKey = k, serverCertKey = ck, sessSecret = Nothing}) <$> ck_
|
||||||
|
in ntfThHandle_ th v thAuth
|
||||||
|
|
||||||
|
ntfThHandle_ :: forall c p. THandleNTF c p -> VersionNTF -> Maybe (THandleAuth p) -> THandleNTF c p
|
||||||
|
ntfThHandle_ th@THandle {params} v thAuth =
|
||||||
|
-- TODO drop SMP v6: make thAuth non-optional
|
||||||
|
let v3 = v >= authBatchCmdsNTFVersion
|
||||||
|
params' = params {thVersion = v, thAuth, implySessId = v3, batch = v3}
|
||||||
|
in (th :: THandleNTF c p) {params = params'}
|
||||||
|
|
||||||
|
ntfTHandle :: Transport c => c -> THandleNTF c p
|
||||||
ntfTHandle c = THandle {connection = c, params}
|
ntfTHandle c = THandle {connection = c, params}
|
||||||
where
|
where
|
||||||
params = THandleParams {sessionId = tlsUnique c, blockSize = ntfBlockSize, thVersion = VersionNTF 0, thAuth = Nothing, implySessId = False, batch = False}
|
params = THandleParams {sessionId = tlsUnique c, blockSize = ntfBlockSize, thVersion = VersionNTF 0, thAuth = Nothing, implySessId = False, batch = False}
|
||||||
|
|||||||
@@ -1075,7 +1075,7 @@ data CommandError
|
|||||||
deriving (Eq, Read, Show)
|
deriving (Eq, Read, Show)
|
||||||
|
|
||||||
-- | SMP transmission parser.
|
-- | SMP transmission parser.
|
||||||
transmissionP :: THandleParams v -> Parser RawTransmission
|
transmissionP :: THandleParams v p -> Parser RawTransmission
|
||||||
transmissionP THandleParams {sessionId, implySessId} = do
|
transmissionP THandleParams {sessionId, implySessId} = do
|
||||||
authenticator <- smpP
|
authenticator <- smpP
|
||||||
authorized <- A.takeByteString
|
authorized <- A.takeByteString
|
||||||
@@ -1089,10 +1089,10 @@ transmissionP THandleParams {sessionId, implySessId} = do
|
|||||||
command <- A.takeByteString
|
command <- A.takeByteString
|
||||||
pure RawTransmission {authenticator, authorized = authorized', sessId, corrId, entityId, command}
|
pure RawTransmission {authenticator, authorized = authorized', sessId, corrId, entityId, command}
|
||||||
|
|
||||||
class (ProtocolEncoding v err msg, ProtocolEncoding v err (ProtoCommand msg), Show err, Show msg) => Protocol v err msg | msg -> v, msg -> err where
|
class (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 ProtoCommand msg = cmd | cmd -> msg
|
||||||
type ProtoType msg = (sch :: ProtocolType) | sch -> msg
|
type ProtoType msg = (sch :: ProtocolType) | sch -> msg
|
||||||
protocolClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRange v -> ExceptT TransportError IO (THandle v c)
|
protocolClientHandshake :: forall c. Transport c => c -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRange v -> ExceptT TransportError IO (THandle v c 'TClient)
|
||||||
protocolPing :: ProtoCommand msg
|
protocolPing :: ProtoCommand msg
|
||||||
protocolError :: msg -> Maybe err
|
protocolError :: msg -> Maybe err
|
||||||
|
|
||||||
@@ -1308,7 +1308,7 @@ instance Encoding CommandError where
|
|||||||
_ -> fail "bad command error type"
|
_ -> fail "bad command error type"
|
||||||
|
|
||||||
-- | Send signed SMP transmission to TCP transport.
|
-- | Send signed SMP transmission to TCP transport.
|
||||||
tPut :: Transport c => THandle v c -> NonEmpty (Either TransportError SentRawTransmission) -> IO [Either TransportError ()]
|
tPut :: Transport c => THandle v c p -> NonEmpty (Either TransportError SentRawTransmission) -> IO [Either TransportError ()]
|
||||||
tPut th@THandle {params} = fmap concat . mapM tPutBatch . batchTransmissions (batch params) (blockSize params)
|
tPut th@THandle {params} = fmap concat . mapM tPutBatch . batchTransmissions (batch params) (blockSize params)
|
||||||
where
|
where
|
||||||
tPutBatch :: TransportBatch () -> IO [Either TransportError ()]
|
tPutBatch :: TransportBatch () -> IO [Either TransportError ()]
|
||||||
@@ -1317,7 +1317,7 @@ tPut th@THandle {params} = fmap concat . mapM tPutBatch . batchTransmissions (ba
|
|||||||
TBTransmissions s n _ -> replicate n <$> tPutLog th s
|
TBTransmissions s n _ -> replicate n <$> tPutLog th s
|
||||||
TBTransmission s _ -> (: []) <$> tPutLog th s
|
TBTransmission s _ -> (: []) <$> tPutLog th s
|
||||||
|
|
||||||
tPutLog :: Transport c => THandle v c -> ByteString -> IO (Either TransportError ())
|
tPutLog :: Transport c => THandle v c p -> ByteString -> IO (Either TransportError ())
|
||||||
tPutLog th s = do
|
tPutLog th s = do
|
||||||
r <- tPutBlock th s
|
r <- tPutBlock th s
|
||||||
case r of
|
case r of
|
||||||
@@ -1383,7 +1383,7 @@ tEncodeBatch1 t = lenEncode 1 `B.cons` tEncodeForBatch t
|
|||||||
-- tForAuth is lazy to avoid computing it when there is no key to sign
|
-- tForAuth is lazy to avoid computing it when there is no key to sign
|
||||||
data TransmissionForAuth = TransmissionForAuth {tForAuth :: ~ByteString, tToSend :: ByteString}
|
data TransmissionForAuth = TransmissionForAuth {tForAuth :: ~ByteString, tToSend :: ByteString}
|
||||||
|
|
||||||
encodeTransmissionForAuth :: ProtocolEncoding v e c => THandleParams v -> Transmission c -> TransmissionForAuth
|
encodeTransmissionForAuth :: ProtocolEncoding v e c => THandleParams v p -> Transmission c -> TransmissionForAuth
|
||||||
encodeTransmissionForAuth THandleParams {thVersion = v, sessionId, implySessId} t =
|
encodeTransmissionForAuth THandleParams {thVersion = v, sessionId, implySessId} t =
|
||||||
TransmissionForAuth {tForAuth, tToSend = if implySessId then t' else tForAuth}
|
TransmissionForAuth {tForAuth, tToSend = if implySessId then t' else tForAuth}
|
||||||
where
|
where
|
||||||
@@ -1391,7 +1391,7 @@ encodeTransmissionForAuth THandleParams {thVersion = v, sessionId, implySessId}
|
|||||||
t' = encodeTransmission_ v t
|
t' = encodeTransmission_ v t
|
||||||
{-# INLINE encodeTransmissionForAuth #-}
|
{-# INLINE encodeTransmissionForAuth #-}
|
||||||
|
|
||||||
encodeTransmission :: ProtocolEncoding v e c => THandleParams v -> Transmission c -> ByteString
|
encodeTransmission :: ProtocolEncoding v e c => THandleParams v p -> Transmission c -> ByteString
|
||||||
encodeTransmission THandleParams {thVersion = v, sessionId, implySessId} t =
|
encodeTransmission THandleParams {thVersion = v, sessionId, implySessId} t =
|
||||||
if implySessId then t' else smpEncode sessionId <> t'
|
if implySessId then t' else smpEncode sessionId <> t'
|
||||||
where
|
where
|
||||||
@@ -1404,11 +1404,11 @@ encodeTransmission_ v (CorrId corrId, queueId, command) =
|
|||||||
{-# INLINE encodeTransmission_ #-}
|
{-# INLINE encodeTransmission_ #-}
|
||||||
|
|
||||||
-- | Receive and parse transmission from the TCP transport (ignoring any trailing padding).
|
-- | Receive and parse transmission from the TCP transport (ignoring any trailing padding).
|
||||||
tGetParse :: Transport c => THandle v c -> IO (NonEmpty (Either TransportError RawTransmission))
|
tGetParse :: Transport c => THandle v c p -> IO (NonEmpty (Either TransportError RawTransmission))
|
||||||
tGetParse th@THandle {params} = eitherList (tParse params) <$> tGetBlock th
|
tGetParse th@THandle {params} = eitherList (tParse params) <$> tGetBlock th
|
||||||
{-# INLINE tGetParse #-}
|
{-# INLINE tGetParse #-}
|
||||||
|
|
||||||
tParse :: THandleParams v -> ByteString -> NonEmpty (Either TransportError RawTransmission)
|
tParse :: THandleParams v p -> ByteString -> NonEmpty (Either TransportError RawTransmission)
|
||||||
tParse thParams@THandleParams {batch} s
|
tParse thParams@THandleParams {batch} s
|
||||||
| batch = eitherList (L.map (\(Large t) -> tParse1 t)) ts
|
| batch = eitherList (L.map (\(Large t) -> tParse1 t)) ts
|
||||||
| otherwise = [tParse1 s]
|
| otherwise = [tParse1 s]
|
||||||
@@ -1420,10 +1420,10 @@ eitherList :: (a -> NonEmpty (Either e b)) -> Either e a -> NonEmpty (Either e b
|
|||||||
eitherList = either (\e -> [Left e])
|
eitherList = either (\e -> [Left e])
|
||||||
|
|
||||||
-- | Receive client and server transmissions (determined by `cmd` type).
|
-- | Receive client and server transmissions (determined by `cmd` type).
|
||||||
tGet :: forall v err cmd c. (ProtocolEncoding v err cmd, Transport c) => THandle v c -> IO (NonEmpty (SignedTransmission err cmd))
|
tGet :: forall v err cmd c p. (ProtocolEncoding v err cmd, Transport c) => THandle v c p -> IO (NonEmpty (SignedTransmission err cmd))
|
||||||
tGet th@THandle {params} = L.map (tDecodeParseValidate params) <$> tGetParse th
|
tGet th@THandle {params} = L.map (tDecodeParseValidate params) <$> tGetParse th
|
||||||
|
|
||||||
tDecodeParseValidate :: forall v err cmd. ProtocolEncoding v err cmd => THandleParams v -> Either TransportError RawTransmission -> SignedTransmission err cmd
|
tDecodeParseValidate :: forall v p err cmd. ProtocolEncoding v err cmd => THandleParams v p -> Either TransportError RawTransmission -> SignedTransmission err cmd
|
||||||
tDecodeParseValidate THandleParams {sessionId, thVersion = v, implySessId} = \case
|
tDecodeParseValidate THandleParams {sessionId, thVersion = v, implySessId} = \case
|
||||||
Right RawTransmission {authenticator, authorized, sessId, corrId, entityId, command}
|
Right RawTransmission {authenticator, authorized, sessId, corrId, entityId, command}
|
||||||
| implySessId || sessId == sessionId ->
|
| implySessId || sessId == sessionId ->
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
logError "Unauthorized control port command"
|
logError "Unauthorized control port command"
|
||||||
hPutStrLn h "AUTH"
|
hPutStrLn h "AUTH"
|
||||||
|
|
||||||
runClientTransport :: Transport c => THandleSMP c -> M ()
|
runClientTransport :: Transport c => THandleSMP c 'TServer -> M ()
|
||||||
runClientTransport th@THandle {params = THandleParams {thVersion, sessionId}} = do
|
runClientTransport th@THandle {params = THandleParams {thVersion, sessionId}} = do
|
||||||
q <- asks $ tbqSize . config
|
q <- asks $ tbqSize . config
|
||||||
ts <- liftIO getSystemTime
|
ts <- liftIO getSystemTime
|
||||||
@@ -457,7 +457,7 @@ cancelSub sub =
|
|||||||
Sub {subThread = SubThread t} -> liftIO $ deRefWeak t >>= mapM_ killThread
|
Sub {subThread = SubThread t} -> liftIO $ deRefWeak t >>= mapM_ killThread
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
receive :: Transport c => THandleSMP c -> Client -> M ()
|
receive :: Transport c => THandleSMP c 'TServer -> Client -> M ()
|
||||||
receive th@THandle {params = THandleParams {thAuth}} Client {rcvQ, sndQ, rcvActiveAt, sessionId} = do
|
receive th@THandle {params = THandleParams {thAuth}} Client {rcvQ, sndQ, rcvActiveAt, sessionId} = do
|
||||||
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " receive"
|
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " receive"
|
||||||
forever $ do
|
forever $ do
|
||||||
@@ -478,7 +478,7 @@ receive th@THandle {params = THandleParams {thAuth}} Client {rcvQ, sndQ, rcvActi
|
|||||||
VRFailed -> Left (corrId, queueId, ERR AUTH)
|
VRFailed -> Left (corrId, queueId, ERR AUTH)
|
||||||
write q = mapM_ (atomically . writeTBQueue q) . L.nonEmpty
|
write q = mapM_ (atomically . writeTBQueue q) . L.nonEmpty
|
||||||
|
|
||||||
send :: Transport c => THandleSMP c -> Client -> IO ()
|
send :: Transport c => THandleSMP c 'TServer -> Client -> IO ()
|
||||||
send h@THandle {params} Client {sndQ, sessionId, sndActiveAt} = do
|
send h@THandle {params} Client {sndQ, sessionId, sndActiveAt} = do
|
||||||
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " send"
|
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " send"
|
||||||
forever $ do
|
forever $ do
|
||||||
@@ -493,7 +493,7 @@ send h@THandle {params} Client {sndQ, sessionId, sndActiveAt} = do
|
|||||||
NMSG {} -> 0
|
NMSG {} -> 0
|
||||||
_ -> 1
|
_ -> 1
|
||||||
|
|
||||||
disconnectTransport :: Transport c => THandle v c -> TVar SystemTime -> TVar SystemTime -> ExpirationConfig -> IO Bool -> IO ()
|
disconnectTransport :: Transport c => THandle v c 'TServer -> TVar SystemTime -> TVar SystemTime -> ExpirationConfig -> IO Bool -> IO ()
|
||||||
disconnectTransport THandle {connection, params = THandleParams {sessionId}} rcvActiveAt sndActiveAt expCfg noSubscriptions = do
|
disconnectTransport THandle {connection, params = THandleParams {sessionId}} rcvActiveAt sndActiveAt expCfg noSubscriptions = do
|
||||||
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " disconnectTransport"
|
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " disconnectTransport"
|
||||||
loop
|
loop
|
||||||
@@ -514,7 +514,7 @@ data VerificationResult = VRVerified (Maybe QueueRec) | VRFailed
|
|||||||
-- - the queue or party key do not exist.
|
-- - the queue or party key do not exist.
|
||||||
-- In all cases, the time of the verification should depend only on the provided authorization type,
|
-- In all cases, the time of the verification should depend only on the provided authorization type,
|
||||||
-- a dummy key is used to run verification in the last two cases, and failure is returned irrespective of the result.
|
-- a dummy key is used to run verification in the last two cases, and failure is returned irrespective of the result.
|
||||||
verifyTransmission :: Maybe (THandleAuth, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> QueueId -> Cmd -> M VerificationResult
|
verifyTransmission :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> QueueId -> Cmd -> M VerificationResult
|
||||||
verifyTransmission auth_ tAuth authorized queueId cmd =
|
verifyTransmission auth_ tAuth authorized queueId cmd =
|
||||||
case cmd of
|
case cmd of
|
||||||
Cmd SRecipient (NEW k _ _ _) -> pure $ Nothing `verifiedWith` k
|
Cmd SRecipient (NEW k _ _ _) -> pure $ Nothing `verifiedWith` k
|
||||||
@@ -536,7 +536,7 @@ verifyTransmission auth_ tAuth authorized queueId cmd =
|
|||||||
st <- asks queueStore
|
st <- asks queueStore
|
||||||
atomically $ getQueue st party queueId
|
atomically $ getQueue st party queueId
|
||||||
|
|
||||||
verifyCmdAuthorization :: Maybe (THandleAuth, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> C.APublicAuthKey -> Bool
|
verifyCmdAuthorization :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> C.APublicAuthKey -> Bool
|
||||||
verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAuth
|
verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAuth
|
||||||
where
|
where
|
||||||
verify :: C.APublicAuthKey -> TransmissionAuth -> Bool
|
verify :: C.APublicAuthKey -> TransmissionAuth -> Bool
|
||||||
@@ -548,12 +548,12 @@ verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAu
|
|||||||
C.SX25519 -> verifyCmdAuth auth_ k s authorized
|
C.SX25519 -> verifyCmdAuth auth_ k s authorized
|
||||||
_ -> verifyCmdAuth auth_ dummyKeyX25519 s authorized `seq` False
|
_ -> verifyCmdAuth auth_ dummyKeyX25519 s authorized `seq` False
|
||||||
|
|
||||||
verifyCmdAuth :: Maybe (THandleAuth, C.CbNonce) -> C.PublicKeyX25519 -> C.CbAuthenticator -> ByteString -> Bool
|
verifyCmdAuth :: Maybe (THandleAuth 'TServer, C.CbNonce) -> C.PublicKeyX25519 -> C.CbAuthenticator -> ByteString -> Bool
|
||||||
verifyCmdAuth auth_ k authenticator authorized = case auth_ of
|
verifyCmdAuth auth_ k authenticator authorized = case auth_ of
|
||||||
Just (THandleAuth {privKey}, nonce) -> C.cbVerify k privKey nonce authenticator authorized
|
Just (THAuthServer {serverPrivKey = pk}, nonce) -> C.cbVerify k pk nonce authenticator authorized
|
||||||
Nothing -> False
|
Nothing -> False
|
||||||
|
|
||||||
dummyVerifyCmd :: Maybe (THandleAuth, C.CbNonce) -> ByteString -> TransmissionAuth -> Bool
|
dummyVerifyCmd :: Maybe (THandleAuth 'TServer, C.CbNonce) -> ByteString -> TransmissionAuth -> Bool
|
||||||
dummyVerifyCmd auth_ authorized = \case
|
dummyVerifyCmd auth_ authorized = \case
|
||||||
TASignature (C.ASignature a s) -> C.verify' (dummySignKey a) s authorized
|
TASignature (C.ASignature a s) -> C.verify' (dummySignKey a) s authorized
|
||||||
TAAuthenticator s -> verifyCmdAuth auth_ dummyKeyX25519 s authorized
|
TAAuthenticator s -> verifyCmdAuth auth_ dummyKeyX25519 s authorized
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
{-# LANGUAGE PatternSynonyms #-}
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
@@ -77,7 +78,7 @@ module Simplex.Messaging.Transport
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Applicative ((<|>))
|
import Control.Applicative (optional, (<|>))
|
||||||
import Control.Monad (forM)
|
import Control.Monad (forM)
|
||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
import Control.Monad.Trans.Except (throwE)
|
import Control.Monad.Trans.Except (throwE)
|
||||||
@@ -311,20 +312,20 @@ instance Transport TLS where
|
|||||||
-- * SMP transport
|
-- * SMP transport
|
||||||
|
|
||||||
-- | The handle for SMP encrypted transport connection over Transport.
|
-- | The handle for SMP encrypted transport connection over Transport.
|
||||||
data THandle v c = THandle
|
data THandle v c p = THandle
|
||||||
{ connection :: c,
|
{ connection :: c,
|
||||||
params :: THandleParams v
|
params :: THandleParams v p
|
||||||
}
|
}
|
||||||
|
|
||||||
type THandleSMP c = THandle SMPVersion c
|
type THandleSMP c p = THandle SMPVersion c p
|
||||||
|
|
||||||
data THandleParams v = THandleParams
|
data THandleParams v p = THandleParams
|
||||||
{ sessionId :: SessionId,
|
{ sessionId :: SessionId,
|
||||||
blockSize :: Int,
|
blockSize :: Int,
|
||||||
-- | agreed server protocol version
|
-- | agreed server protocol version
|
||||||
thVersion :: Version v,
|
thVersion :: Version v,
|
||||||
-- | peer public key for command authorization and shared secrets for entity ID encryption
|
-- | peer public key for command authorization and shared secrets for entity ID encryption
|
||||||
thAuth :: Maybe THandleAuth,
|
thAuth :: Maybe (THandleAuth p),
|
||||||
-- | do NOT send session ID in transmission, but include it into signed message
|
-- | do NOT send session ID in transmission, but include it into signed message
|
||||||
-- based on protocol version
|
-- based on protocol version
|
||||||
implySessId :: Bool,
|
implySessId :: Bool,
|
||||||
@@ -333,10 +334,18 @@ data THandleParams v = THandleParams
|
|||||||
batch :: Bool
|
batch :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
data THandleAuth = THandleAuth
|
data THandleAuth (p :: TransportPeer) where
|
||||||
{ peerPubKey :: C.PublicKeyX25519, -- used only in the client to combine with per-queue key
|
THAuthClient ::
|
||||||
privKey :: C.PrivateKeyX25519 -- used to combine with peer's per-queue key (currently only in the server)
|
{ serverPeerPubKey :: C.PublicKeyX25519, -- used by the client to combine with client's private per-queue key
|
||||||
}
|
serverCertKey :: (X.CertificateChain, X.SignedExact X.PubKey), -- the key here is serverPeerPubKey signed with server certificate
|
||||||
|
sessSecret :: Maybe C.DhSecretX25519 -- session secret (will be used in SMP proxy only)
|
||||||
|
} ->
|
||||||
|
THandleAuth 'TClient
|
||||||
|
THAuthServer ::
|
||||||
|
{ serverPrivKey :: C.PrivateKeyX25519, -- used by the server to combine with client's public per-queue key
|
||||||
|
sessSecret' :: Maybe C.DhSecretX25519 -- session secret (will be used in SMP proxy only)
|
||||||
|
} ->
|
||||||
|
THandleAuth 'TServer
|
||||||
|
|
||||||
-- | TLS-unique channel binding
|
-- | TLS-unique channel binding
|
||||||
type SessionId = ByteString
|
type SessionId = ByteString
|
||||||
@@ -390,7 +399,7 @@ encodeAuthEncryptCmds v k
|
|||||||
| otherwise = ""
|
| otherwise = ""
|
||||||
|
|
||||||
authEncryptCmdsP :: VersionSMP -> Parser a -> Parser (Maybe a)
|
authEncryptCmdsP :: VersionSMP -> Parser a -> Parser (Maybe a)
|
||||||
authEncryptCmdsP v p = if v >= authCmdsSMPVersion then Just <$> p else pure Nothing
|
authEncryptCmdsP v p = if v >= authCmdsSMPVersion then optional p else pure Nothing
|
||||||
|
|
||||||
-- | Error of SMP encrypted transport over TCP.
|
-- | Error of SMP encrypted transport over TCP.
|
||||||
data TransportError
|
data TransportError
|
||||||
@@ -438,13 +447,13 @@ serializeTransportError = \case
|
|||||||
TEHandshake e -> "HANDSHAKE " <> bshow e
|
TEHandshake e -> "HANDSHAKE " <> bshow e
|
||||||
|
|
||||||
-- | Pad and send block to SMP transport.
|
-- | Pad and send block to SMP transport.
|
||||||
tPutBlock :: Transport c => THandle v c -> ByteString -> IO (Either TransportError ())
|
tPutBlock :: Transport c => THandle v c p -> ByteString -> IO (Either TransportError ())
|
||||||
tPutBlock THandle {connection = c, params = THandleParams {blockSize}} block =
|
tPutBlock THandle {connection = c, params = THandleParams {blockSize}} block =
|
||||||
bimapM (const $ pure TELargeMsg) (cPut c) $
|
bimapM (const $ pure TELargeMsg) (cPut c) $
|
||||||
C.pad block blockSize
|
C.pad block blockSize
|
||||||
|
|
||||||
-- | Receive block from SMP transport.
|
-- | Receive block from SMP transport.
|
||||||
tGetBlock :: Transport c => THandle v c -> IO (Either TransportError ByteString)
|
tGetBlock :: Transport c => THandle v c p -> IO (Either TransportError ByteString)
|
||||||
tGetBlock THandle {connection = c, params = THandleParams {blockSize}} = do
|
tGetBlock THandle {connection = c, params = THandleParams {blockSize}} = do
|
||||||
msg <- cGet c blockSize
|
msg <- cGet c blockSize
|
||||||
if B.length msg == blockSize
|
if B.length msg == blockSize
|
||||||
@@ -454,7 +463,7 @@ tGetBlock THandle {connection = c, params = THandleParams {blockSize}} = do
|
|||||||
-- | Server SMP transport handshake.
|
-- | Server SMP transport handshake.
|
||||||
--
|
--
|
||||||
-- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a
|
-- 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)
|
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 serverSignKey c (k, pk) kh smpVRange = do
|
||||||
let th@THandle {params = THandleParams {sessionId}} = smpTHandle c
|
let th@THandle {params = THandleParams {sessionId}} = smpTHandle c
|
||||||
sk = C.signX509 serverSignKey $ C.publicToX509 k
|
sk = C.signX509 serverSignKey $ C.publicToX509 k
|
||||||
@@ -465,47 +474,56 @@ smpServerHandshake serverSignKey c (k, pk) kh smpVRange = do
|
|||||||
| keyHash /= kh ->
|
| keyHash /= kh ->
|
||||||
throwE $ TEHandshake IDENTITY
|
throwE $ TEHandshake IDENTITY
|
||||||
| v `isCompatible` smpVRange ->
|
| v `isCompatible` smpVRange ->
|
||||||
pure $ smpThHandle th v pk k'
|
pure $ smpThHandleServer th v pk k'
|
||||||
| otherwise -> throwE $ TEHandshake VERSION
|
| otherwise -> throwE $ TEHandshake VERSION
|
||||||
|
|
||||||
-- | Client SMP transport handshake.
|
-- | Client SMP transport handshake.
|
||||||
--
|
--
|
||||||
-- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a
|
-- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a
|
||||||
smpClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c)
|
smpClientHandshake :: forall c. Transport c => c -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c 'TClient)
|
||||||
smpClientHandshake c (k, pk) keyHash@(C.KeyHash kh) smpVRange = do
|
smpClientHandshake c ks_ keyHash@(C.KeyHash kh) smpVRange = do
|
||||||
let th@THandle {params = THandleParams {sessionId}} = smpTHandle c
|
let th@THandle {params = THandleParams {sessionId}} = smpTHandle c
|
||||||
ServerHandshake {sessionId = sessId, smpVersionRange, authPubKey} <- getHandshake th
|
ServerHandshake {sessionId = sessId, smpVersionRange, authPubKey} <- getHandshake th
|
||||||
if sessionId /= sessId
|
if sessionId /= sessId
|
||||||
then throwE TEBadSession
|
then throwE TEBadSession
|
||||||
else case smpVersionRange `compatibleVersion` smpVRange of
|
else case smpVersionRange `compatibleVersion` smpVRange of
|
||||||
Just (Compatible v) -> do
|
Just (Compatible v) -> do
|
||||||
sk_ <- forM authPubKey $ \(X.CertificateChain cert, exact) ->
|
ck_ <- forM authPubKey $ \certKey@(X.CertificateChain cert, exact) ->
|
||||||
liftEitherWith (const $ TEHandshake BAD_AUTH) $ do
|
liftEitherWith (const $ TEHandshake BAD_AUTH) $ do
|
||||||
case cert of
|
case cert of
|
||||||
[_leaf, ca] | XV.Fingerprint kh == XV.getFingerprint ca X.HashSHA256 -> pure ()
|
[_leaf, ca] | XV.Fingerprint kh == XV.getFingerprint ca X.HashSHA256 -> pure ()
|
||||||
_ -> throwError "bad certificate"
|
_ -> throwError "bad certificate"
|
||||||
serverKey <- getServerVerifyKey c
|
serverKey <- getServerVerifyKey c
|
||||||
pubKey <- C.verifyX509 serverKey exact
|
pubKey <- C.verifyX509 serverKey exact
|
||||||
C.x509ToPublic (pubKey, []) >>= C.pubKey
|
(,certKey) <$> (C.x509ToPublic (pubKey, []) >>= C.pubKey)
|
||||||
sendHandshake th $ ClientHandshake {smpVersion = v, keyHash, authPubKey = Just k}
|
sendHandshake th $ ClientHandshake {smpVersion = v, keyHash, authPubKey = fst <$> ks_}
|
||||||
pure $ smpThHandle th v pk sk_
|
pure $ smpThHandleClient th v (snd <$> ks_) ck_
|
||||||
Nothing -> throwE $ TEHandshake VERSION
|
Nothing -> throwE $ TEHandshake VERSION
|
||||||
|
|
||||||
smpThHandle :: forall c. THandleSMP c -> VersionSMP -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleSMP c
|
smpThHandleServer :: forall c. THandleSMP c 'TServer -> VersionSMP -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleSMP c 'TServer
|
||||||
smpThHandle th@THandle {params} v privKey k_ =
|
smpThHandleServer th v pk k_ =
|
||||||
-- TODO drop SMP v6: make thAuth non-optional
|
let thAuth = THAuthServer {serverPrivKey = pk, sessSecret' = (`C.dh'` pk) <$> k_}
|
||||||
let thAuth = (\k -> THandleAuth {peerPubKey = k, privKey}) <$> k_
|
in smpThHandle_ th v (Just thAuth)
|
||||||
params' = params {thVersion = v, thAuth, implySessId = v >= authCmdsSMPVersion}
|
|
||||||
in (th :: THandleSMP c) {params = params'}
|
|
||||||
|
|
||||||
sendHandshake :: (Transport c, Encoding smp) => THandle v c -> smp -> ExceptT TransportError IO ()
|
smpThHandleClient :: forall c. THandleSMP c 'TClient -> VersionSMP -> Maybe C.PrivateKeyX25519 -> Maybe (C.PublicKeyX25519, (X.CertificateChain, X.SignedExact X.PubKey)) -> THandleSMP c 'TClient
|
||||||
|
smpThHandleClient th v pk_ ck_ =
|
||||||
|
let thAuth = (\(k, ck) -> THAuthClient {serverPeerPubKey = k, serverCertKey = ck, sessSecret = C.dh' k <$> pk_}) <$> ck_
|
||||||
|
in smpThHandle_ th v thAuth
|
||||||
|
|
||||||
|
smpThHandle_ :: forall c p. THandleSMP c p -> VersionSMP -> Maybe (THandleAuth p) -> THandleSMP c p
|
||||||
|
smpThHandle_ th@THandle {params} v thAuth =
|
||||||
|
-- TODO drop SMP v6: make thAuth non-optional
|
||||||
|
let params' = params {thVersion = v, thAuth, implySessId = v >= authCmdsSMPVersion}
|
||||||
|
in (th :: THandleSMP c p) {params = params'}
|
||||||
|
|
||||||
|
sendHandshake :: (Transport c, Encoding smp) => THandle v c p -> smp -> ExceptT TransportError IO ()
|
||||||
sendHandshake th = ExceptT . tPutBlock th . smpEncode
|
sendHandshake th = ExceptT . tPutBlock th . smpEncode
|
||||||
|
|
||||||
-- ignores tail bytes to allow future extensions
|
-- ignores tail bytes to allow future extensions
|
||||||
getHandshake :: (Transport c, Encoding smp) => THandle v c -> ExceptT TransportError IO smp
|
getHandshake :: (Transport c, Encoding smp) => THandle v c p -> ExceptT TransportError IO smp
|
||||||
getHandshake th = ExceptT $ (first (\_ -> TEHandshake PARSE) . A.parseOnly smpP =<<) <$> tGetBlock th
|
getHandshake th = ExceptT $ (first (\_ -> TEHandshake PARSE) . A.parseOnly smpP =<<) <$> tGetBlock th
|
||||||
|
|
||||||
smpTHandle :: Transport c => c -> THandleSMP c
|
smpTHandle :: Transport c => c -> THandleSMP c p
|
||||||
smpTHandle c = THandle {connection = c, params}
|
smpTHandle c = THandle {connection = c, params}
|
||||||
where
|
where
|
||||||
params = THandleParams {sessionId = tlsUnique c, blockSize = smpBlockSize, thVersion = VersionSMP 0, thAuth = Nothing, implySessId = False, batch = True}
|
params = THandleParams {sessionId = tlsUnique c, blockSize = smpBlockSize, thVersion = VersionSMP 0, thAuth = Nothing, implySessId = False, batch = True}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ module Simplex.Messaging.Transport.Client
|
|||||||
runTLSTransportClient,
|
runTLSTransportClient,
|
||||||
smpClientHandshake,
|
smpClientHandshake,
|
||||||
defaultSMPPort,
|
defaultSMPPort,
|
||||||
|
defaultTcpConnectTimeout,
|
||||||
defaultTransportClientConfig,
|
defaultTransportClientConfig,
|
||||||
defaultSocksProxy,
|
defaultSocksProxy,
|
||||||
TransportClientConfig (..),
|
TransportClientConfig (..),
|
||||||
@@ -52,6 +53,7 @@ import Simplex.Messaging.Transport
|
|||||||
import Simplex.Messaging.Transport.KeepAlive
|
import Simplex.Messaging.Transport.KeepAlive
|
||||||
import Simplex.Messaging.Util (bshow, (<$?>), catchAll, tshow)
|
import Simplex.Messaging.Util (bshow, (<$?>), catchAll, tshow)
|
||||||
import System.IO.Error
|
import System.IO.Error
|
||||||
|
import System.Timeout (timeout)
|
||||||
import Text.Read (readMaybe)
|
import Text.Read (readMaybe)
|
||||||
import UnliftIO.Exception (IOException)
|
import UnliftIO.Exception (IOException)
|
||||||
import qualified UnliftIO.Exception as E
|
import qualified UnliftIO.Exception as E
|
||||||
@@ -112,6 +114,7 @@ instance IsString (NonEmpty TransportHost) where fromString = parseString strDec
|
|||||||
|
|
||||||
data TransportClientConfig = TransportClientConfig
|
data TransportClientConfig = TransportClientConfig
|
||||||
{ socksProxy :: Maybe SocksProxy,
|
{ socksProxy :: Maybe SocksProxy,
|
||||||
|
tcpConnectTimeout :: Int,
|
||||||
tcpKeepAlive :: Maybe KeepAliveOpts,
|
tcpKeepAlive :: Maybe KeepAliveOpts,
|
||||||
logTLSErrors :: Bool,
|
logTLSErrors :: Bool,
|
||||||
clientCredentials :: Maybe (X.CertificateChain, T.PrivKey),
|
clientCredentials :: Maybe (X.CertificateChain, T.PrivKey),
|
||||||
@@ -119,8 +122,12 @@ data TransportClientConfig = TransportClientConfig
|
|||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
-- time to resolve host, connect socket, set up TLS
|
||||||
|
defaultTcpConnectTimeout :: Int
|
||||||
|
defaultTcpConnectTimeout = 10000000
|
||||||
|
|
||||||
defaultTransportClientConfig :: TransportClientConfig
|
defaultTransportClientConfig :: TransportClientConfig
|
||||||
defaultTransportClientConfig = TransportClientConfig Nothing (Just defaultKeepAliveOpts) True Nothing Nothing
|
defaultTransportClientConfig = TransportClientConfig Nothing defaultTcpConnectTimeout (Just defaultKeepAliveOpts) True Nothing Nothing
|
||||||
|
|
||||||
clientTransportConfig :: TransportClientConfig -> TransportConfig
|
clientTransportConfig :: TransportClientConfig -> TransportConfig
|
||||||
clientTransportConfig TransportClientConfig {logTLSErrors} =
|
clientTransportConfig TransportClientConfig {logTLSErrors} =
|
||||||
@@ -131,7 +138,7 @@ runTransportClient :: Transport c => TransportClientConfig -> Maybe ByteString -
|
|||||||
runTransportClient = runTLSTransportClient supportedParameters Nothing
|
runTransportClient = runTLSTransportClient supportedParameters Nothing
|
||||||
|
|
||||||
runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
|
runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
|
||||||
runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpKeepAlive, clientCredentials, alpn} proxyUsername host port keyHash client = do
|
runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpConnectTimeout, tcpKeepAlive, clientCredentials, alpn} proxyUsername host port keyHash client = do
|
||||||
serverCert <- newEmptyTMVarIO
|
serverCert <- newEmptyTMVarIO
|
||||||
let hostName = B.unpack $ strEncode host
|
let hostName = B.unpack $ strEncode host
|
||||||
clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert
|
clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert
|
||||||
@@ -142,13 +149,19 @@ runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy,
|
|||||||
sock <- connectTCP port
|
sock <- connectTCP port
|
||||||
mapM_ (setSocketKeepAlive sock) tcpKeepAlive `catchAll` \e -> logError ("Error setting TCP keep-alive" <> tshow e)
|
mapM_ (setSocketKeepAlive sock) tcpKeepAlive `catchAll` \e -> logError ("Error setting TCP keep-alive" <> tshow e)
|
||||||
let tCfg = clientTransportConfig cfg
|
let tCfg = clientTransportConfig cfg
|
||||||
connectTLS (Just hostName) tCfg clientParams sock >>= \tls -> do
|
timeout tcpConnectTimeout (connectTLS (Just hostName) tCfg clientParams sock) >>= \case
|
||||||
chain <- atomically (tryTakeTMVar serverCert) >>= \case
|
Nothing -> do
|
||||||
Nothing -> do
|
close sock
|
||||||
logError "onServerCertificate didn't fire or failed to get cert chain"
|
logError "connection timed out"
|
||||||
closeTLS tls >> error "onServerCertificate failed"
|
fail "connection timed out"
|
||||||
Just c -> pure c
|
Just tls -> do
|
||||||
getClientConnection tCfg chain tls
|
chain <-
|
||||||
|
atomically (tryTakeTMVar serverCert) >>= \case
|
||||||
|
Nothing -> do
|
||||||
|
logError "onServerCertificate didn't fire or failed to get cert chain"
|
||||||
|
closeTLS tls >> error "onServerCertificate failed"
|
||||||
|
Just c -> pure c
|
||||||
|
getClientConnection tCfg chain tls
|
||||||
client c `E.finally` closeConnection c
|
client c `E.finally` closeConnection c
|
||||||
where
|
where
|
||||||
hostAddr = \case
|
hostAddr = \case
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import Numeric.Natural (Natural)
|
|||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Transport (ALPN, SessionId, TLS (tlsALPN), getServerCerts, getServerVerifyKey, tlsUniq)
|
import Simplex.Messaging.Transport (ALPN, SessionId, TLS (tlsALPN), getServerCerts, getServerVerifyKey, tlsUniq)
|
||||||
import Simplex.Messaging.Transport.Client (TransportClientConfig (..), TransportHost (..), runTLSTransportClient)
|
import Simplex.Messaging.Transport.Client (TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTLSTransportClient)
|
||||||
import Simplex.Messaging.Transport.HTTP2
|
import Simplex.Messaging.Transport.HTTP2
|
||||||
import Simplex.Messaging.Util (eitherToMaybe)
|
import Simplex.Messaging.Util (eitherToMaybe)
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
@@ -71,7 +71,15 @@ defaultHTTP2ClientConfig =
|
|||||||
HTTP2ClientConfig
|
HTTP2ClientConfig
|
||||||
{ qSize = 64,
|
{ qSize = 64,
|
||||||
connTimeout = 10000000,
|
connTimeout = 10000000,
|
||||||
transportConfig = TransportClientConfig Nothing Nothing True Nothing Nothing,
|
transportConfig =
|
||||||
|
TransportClientConfig
|
||||||
|
{ socksProxy = Nothing,
|
||||||
|
tcpConnectTimeout = defaultTcpConnectTimeout,
|
||||||
|
tcpKeepAlive = Nothing,
|
||||||
|
logTLSErrors = True,
|
||||||
|
clientCredentials = Nothing,
|
||||||
|
alpn = Nothing
|
||||||
|
},
|
||||||
bufferSize = defaultHTTP2BufferSize,
|
bufferSize = defaultHTTP2BufferSize,
|
||||||
bodyHeadSize = 16384,
|
bodyHeadSize = 16384,
|
||||||
suportedTLSParams = http2TLSParams
|
suportedTLSParams = http2TLSParams
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
{-# LANGUAGE GADTs #-}
|
{-# LANGUAGE GADTs #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
module CoreTests.BatchingTests (batchingTests) where
|
module CoreTests.BatchingTests (batchingTests) where
|
||||||
|
|
||||||
@@ -11,6 +13,9 @@ import Crypto.Random (ChaChaDRG)
|
|||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
|
import qualified Data.X509 as X
|
||||||
|
import qualified Data.X509.CertificateStore as XS
|
||||||
|
import qualified Data.X509.File as XF
|
||||||
import Simplex.Messaging.Client
|
import Simplex.Messaging.Client
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Protocol
|
import Simplex.Messaging.Protocol
|
||||||
@@ -314,7 +319,7 @@ randomSEND_ a v sessId len = do
|
|||||||
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, sId, Cmd SSender $ SEND noMsgFlags msg)
|
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, sId, Cmd SSender $ SEND noMsgFlags msg)
|
||||||
pure $ (,tToSend) <$> authTransmission thAuth_ (Just spKey) corrId tForAuth
|
pure $ (,tToSend) <$> authTransmission thAuth_ (Just spKey) corrId tForAuth
|
||||||
|
|
||||||
testTHandleParams :: VersionSMP -> ByteString -> THandleParams SMPVersion
|
testTHandleParams :: VersionSMP -> ByteString -> THandleParams SMPVersion 'TClient
|
||||||
testTHandleParams v sessionId =
|
testTHandleParams v sessionId =
|
||||||
THandleParams
|
THandleParams
|
||||||
{ sessionId,
|
{ sessionId,
|
||||||
@@ -325,11 +330,16 @@ testTHandleParams v sessionId =
|
|||||||
batch = True
|
batch = True
|
||||||
}
|
}
|
||||||
|
|
||||||
testTHandleAuth :: VersionSMP -> TVar ChaChaDRG -> C.APublicAuthKey -> IO (Maybe THandleAuth)
|
testTHandleAuth :: VersionSMP -> TVar ChaChaDRG -> C.APublicAuthKey -> IO (Maybe (THandleAuth 'TClient))
|
||||||
testTHandleAuth v g (C.APublicAuthKey a k) = case a of
|
testTHandleAuth v g (C.APublicAuthKey a serverPeerPubKey) = case a of
|
||||||
C.SX25519 | v >= authCmdsSMPVersion -> do
|
C.SX25519 | v >= authCmdsSMPVersion -> do
|
||||||
(_, privKey) <- atomically $ C.generateKeyPair g
|
ca <- head <$> XS.readCertificates "tests/fixtures/ca.crt"
|
||||||
pure $ Just THandleAuth {peerPubKey = k, privKey}
|
serverCert <- head <$> XS.readCertificates "tests/fixtures/server.crt"
|
||||||
|
serverKey <- head <$> XF.readKeyFile "tests/fixtures/server.key"
|
||||||
|
signKey <- either error pure $ C.x509ToPrivate (serverKey, []) >>= C.privKey @C.APrivateSignKey
|
||||||
|
(serverAuthPub, _) <- atomically $ C.generateKeyPair @'C.X25519 g
|
||||||
|
let serverCertKey = (X.CertificateChain [serverCert, ca], C.signX509 signKey $ C.toPubKey C.publicToX509 serverAuthPub)
|
||||||
|
pure $ Just THAuthClient {serverPeerPubKey, serverCertKey, sessSecret = Nothing}
|
||||||
_ -> pure Nothing
|
_ -> pure Nothing
|
||||||
|
|
||||||
randomSENDCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> Int -> IO (PCTransmission ErrorType BrokerMsg)
|
randomSENDCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> Int -> IO (PCTransmission ErrorType BrokerMsg)
|
||||||
|
|||||||
+6
-8
@@ -70,13 +70,11 @@ testKeyHash = "LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI="
|
|||||||
ntfTestStoreLogFile :: FilePath
|
ntfTestStoreLogFile :: FilePath
|
||||||
ntfTestStoreLogFile = "tests/tmp/ntf-server-store.log"
|
ntfTestStoreLogFile = "tests/tmp/ntf-server-store.log"
|
||||||
|
|
||||||
testNtfClient :: Transport c => (THandleNTF c -> IO a) -> IO a
|
testNtfClient :: Transport c => (THandleNTF c 'TClient -> IO a) -> IO a
|
||||||
testNtfClient client = do
|
testNtfClient client = do
|
||||||
Right host <- pure $ chooseTransportHost defaultNetworkConfig testHost
|
Right host <- pure $ chooseTransportHost defaultNetworkConfig testHost
|
||||||
runTransportClient defaultTransportClientConfig Nothing host ntfTestPort (Just testKeyHash) $ \h -> do
|
runTransportClient defaultTransportClientConfig Nothing host ntfTestPort (Just testKeyHash) $ \h ->
|
||||||
g <- C.newRandom
|
runExceptT (ntfClientHandshake h testKeyHash supportedClientNTFVRange) >>= \case
|
||||||
ks <- atomically $ C.generateKeyPair g
|
|
||||||
runExceptT (ntfClientHandshake h ks testKeyHash supportedClientNTFVRange) >>= \case
|
|
||||||
Right th -> client th
|
Right th -> client th
|
||||||
Left e -> error $ show e
|
Left e -> error $ show e
|
||||||
|
|
||||||
@@ -139,7 +137,7 @@ withNtfServerOn t port' = withNtfServerThreadOn t port' . const
|
|||||||
withNtfServer :: ATransport -> IO a -> IO a
|
withNtfServer :: ATransport -> IO a -> IO a
|
||||||
withNtfServer t = withNtfServerOn t ntfTestPort
|
withNtfServer t = withNtfServerOn t ntfTestPort
|
||||||
|
|
||||||
runNtfTest :: forall c a. Transport c => (THandleNTF c -> IO a) -> IO a
|
runNtfTest :: forall c a. Transport c => (THandleNTF c 'TClient -> IO a) -> IO a
|
||||||
runNtfTest test = withNtfServer (transport @c) $ testNtfClient test
|
runNtfTest test = withNtfServer (transport @c) $ testNtfClient test
|
||||||
|
|
||||||
ntfServerTest ::
|
ntfServerTest ::
|
||||||
@@ -150,7 +148,7 @@ ntfServerTest ::
|
|||||||
IO (Maybe TransmissionAuth, ByteString, ByteString, NtfResponse)
|
IO (Maybe TransmissionAuth, ByteString, ByteString, NtfResponse)
|
||||||
ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h
|
ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h
|
||||||
where
|
where
|
||||||
tPut' :: THandleNTF c -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO ()
|
tPut' :: THandleNTF c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO ()
|
||||||
tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do
|
tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do
|
||||||
let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp)
|
let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp)
|
||||||
[Right ()] <- tPut h [Right (sig, t')]
|
[Right ()] <- tPut h [Right (sig, t')]
|
||||||
@@ -159,7 +157,7 @@ ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h
|
|||||||
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
|
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
|
||||||
pure (Nothing, corrId, qId, cmd)
|
pure (Nothing, corrId, qId, cmd)
|
||||||
|
|
||||||
ntfTest :: Transport c => TProxy c -> (THandleNTF c -> IO ()) -> Expectation
|
ntfTest :: Transport c => TProxy c -> (THandleNTF c 'TClient -> IO ()) -> Expectation
|
||||||
ntfTest _ test' = runNtfTest test' `shouldReturn` ()
|
ntfTest _ test' = runNtfTest test' `shouldReturn` ()
|
||||||
|
|
||||||
data APNSMockRequest = APNSMockRequest
|
data APNSMockRequest = APNSMockRequest
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
{-# LANGUAGE PatternSynonyms #-}
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE StandaloneDeriving #-}
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
|
|
||||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
|
||||||
|
|
||||||
module NtfServerTests where
|
module NtfServerTests where
|
||||||
|
|
||||||
@@ -72,13 +72,13 @@ pattern RespNtf corrId queueId command <- (_, _, (corrId, queueId, Right command
|
|||||||
|
|
||||||
deriving instance Eq NtfResponse
|
deriving instance Eq NtfResponse
|
||||||
|
|
||||||
sendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c -> (Maybe TransmissionAuth, ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse)
|
sendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse)
|
||||||
sendRecvNtf h@THandle {params} (sgn, corrId, qId, cmd) = do
|
sendRecvNtf h@THandle {params} (sgn, corrId, qId, cmd) = do
|
||||||
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
||||||
Right () <- tPut1 h (sgn, tToSend)
|
Right () <- tPut1 h (sgn, tToSend)
|
||||||
tGet1 h
|
tGet1 h
|
||||||
|
|
||||||
signSendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c -> C.APrivateAuthKey -> (ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse)
|
signSendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c 'TClient -> C.APrivateAuthKey -> (ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse)
|
||||||
signSendRecvNtf h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
|
signSendRecvNtf h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
|
||||||
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
||||||
Right () <- tPut1 h (authorize tForAuth, tToSend)
|
Right () <- tPut1 h (authorize tForAuth, tToSend)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import Simplex.Messaging.Agent.RetryInterval
|
|||||||
import Simplex.Messaging.Agent.Server (runSMPAgentBlocking)
|
import Simplex.Messaging.Agent.Server (runSMPAgentBlocking)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), SQLiteStore (dbNew))
|
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), SQLiteStore (dbNew))
|
||||||
import Simplex.Messaging.Agent.Store.SQLite.Common (withTransaction')
|
import Simplex.Messaging.Agent.Store.SQLite.Common (withTransaction')
|
||||||
import Simplex.Messaging.Client (ProtocolClientConfig (..), chooseTransportHost, defaultSMPClientConfig, defaultNetworkConfig)
|
import Simplex.Messaging.Client (ProtocolClientConfig (..), chooseTransportHost, defaultNetworkConfig, defaultSMPClientConfig)
|
||||||
import Simplex.Messaging.Notifications.Client (defaultNTFClientConfig)
|
import Simplex.Messaging.Notifications.Client (defaultNTFClientConfig)
|
||||||
import Simplex.Messaging.Parsers (parseAll)
|
import Simplex.Messaging.Parsers (parseAll)
|
||||||
import Simplex.Messaging.Protocol (NtfServer, ProtoServerWithAuth)
|
import Simplex.Messaging.Protocol (NtfServer, ProtoServerWithAuth)
|
||||||
@@ -215,7 +215,7 @@ agentCfg =
|
|||||||
certificateFile = "tests/fixtures/server.crt"
|
certificateFile = "tests/fixtures/server.crt"
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
networkConfig = defaultNetworkConfig {tcpConnectTimeout = 3_000_000, tcpTimeout = 2_000_000}
|
networkConfig = defaultNetworkConfig {tcpConnectTimeout = 1_000_000, tcpTimeout = 2_000_000}
|
||||||
|
|
||||||
fastRetryInterval :: RetryInterval
|
fastRetryInterval :: RetryInterval
|
||||||
fastRetryInterval = defaultReconnectInterval {initialInterval = 50_000}
|
fastRetryInterval = defaultReconnectInterval {initialInterval = 50_000}
|
||||||
|
|||||||
+18
-20
@@ -67,16 +67,14 @@ xit'' d t = do
|
|||||||
ci <- runIO $ lookupEnv "CI"
|
ci <- runIO $ lookupEnv "CI"
|
||||||
(if ci == Just "true" then skip "skipped on CI" . it d else it d) t
|
(if ci == Just "true" then skip "skipped on CI" . it d else it d) t
|
||||||
|
|
||||||
testSMPClient :: Transport c => (THandleSMP c -> IO a) -> IO a
|
testSMPClient :: Transport c => (THandleSMP c 'TClient -> IO a) -> IO a
|
||||||
testSMPClient = testSMPClientVR supportedClientSMPRelayVRange
|
testSMPClient = testSMPClientVR supportedClientSMPRelayVRange
|
||||||
|
|
||||||
testSMPClientVR :: Transport c => VersionRangeSMP -> (THandleSMP c -> IO a) -> IO a
|
testSMPClientVR :: Transport c => VersionRangeSMP -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||||
testSMPClientVR vr client = do
|
testSMPClientVR vr client = do
|
||||||
Right useHost <- pure $ chooseTransportHost defaultNetworkConfig testHost
|
Right useHost <- pure $ chooseTransportHost defaultNetworkConfig testHost
|
||||||
runTransportClient defaultTransportClientConfig Nothing useHost testPort (Just testKeyHash) $ \h -> do
|
runTransportClient defaultTransportClientConfig Nothing useHost testPort (Just testKeyHash) $ \h ->
|
||||||
g <- C.newRandom
|
runExceptT (smpClientHandshake h Nothing testKeyHash vr) >>= \case
|
||||||
ks <- atomically $ C.generateKeyPair g
|
|
||||||
runExceptT (smpClientHandshake h ks testKeyHash vr) >>= \case
|
|
||||||
Right th -> client th
|
Right th -> client th
|
||||||
Left e -> error $ show e
|
Left e -> error $ show e
|
||||||
|
|
||||||
@@ -150,16 +148,16 @@ withSmpServer t = withSmpServerOn t testPort
|
|||||||
withSmpServerV7 :: HasCallStack => ATransport -> IO a -> IO a
|
withSmpServerV7 :: HasCallStack => ATransport -> IO a -> IO a
|
||||||
withSmpServerV7 t = withSmpServerConfigOn t cfgV7 testPort . const
|
withSmpServerV7 t = withSmpServerConfigOn t cfgV7 testPort . const
|
||||||
|
|
||||||
runSmpTest :: forall c a. (HasCallStack, Transport c) => (HasCallStack => THandleSMP c -> IO a) -> IO a
|
runSmpTest :: forall c a. (HasCallStack, Transport c) => (HasCallStack => THandleSMP c 'TClient -> IO a) -> IO a
|
||||||
runSmpTest test = withSmpServer (transport @c) $ testSMPClient test
|
runSmpTest test = withSmpServer (transport @c) $ testSMPClient test
|
||||||
|
|
||||||
runSmpTestN :: forall c a. (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c] -> IO a) -> IO a
|
runSmpTestN :: forall c a. (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO a) -> IO a
|
||||||
runSmpTestN = runSmpTestNCfg cfg supportedClientSMPRelayVRange
|
runSmpTestN = runSmpTestNCfg cfg supportedClientSMPRelayVRange
|
||||||
|
|
||||||
runSmpTestNCfg :: forall c a. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> Int -> (HasCallStack => [THandleSMP c] -> IO a) -> IO a
|
runSmpTestNCfg :: forall c a. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO a) -> IO a
|
||||||
runSmpTestNCfg srvCfg clntVR nClients test = withSmpServerConfigOn (transport @c) srvCfg testPort $ \_ -> run nClients []
|
runSmpTestNCfg srvCfg clntVR nClients test = withSmpServerConfigOn (transport @c) srvCfg testPort $ \_ -> run nClients []
|
||||||
where
|
where
|
||||||
run :: Int -> [THandleSMP c] -> IO a
|
run :: Int -> [THandleSMP c 'TClient] -> IO a
|
||||||
run 0 hs = test hs
|
run 0 hs = test hs
|
||||||
run n hs = testSMPClientVR clntVR $ \h -> run (n - 1) (h : hs)
|
run n hs = testSMPClientVR clntVR $ \h -> run (n - 1) (h : hs)
|
||||||
|
|
||||||
@@ -171,7 +169,7 @@ smpServerTest ::
|
|||||||
IO (Maybe TransmissionAuth, ByteString, ByteString, BrokerMsg)
|
IO (Maybe TransmissionAuth, ByteString, ByteString, BrokerMsg)
|
||||||
smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h
|
smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h
|
||||||
where
|
where
|
||||||
tPut' :: THandleSMP c -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO ()
|
tPut' :: THandleSMP c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO ()
|
||||||
tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do
|
tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do
|
||||||
let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp)
|
let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp)
|
||||||
[Right ()] <- tPut h [Right (sig, t')]
|
[Right ()] <- tPut h [Right (sig, t')]
|
||||||
@@ -180,33 +178,33 @@ smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h
|
|||||||
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
|
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
|
||||||
pure (Nothing, corrId, qId, cmd)
|
pure (Nothing, corrId, qId, cmd)
|
||||||
|
|
||||||
smpTest :: (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> IO ()) -> Expectation
|
smpTest :: (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
smpTest _ test' = runSmpTest test' `shouldReturn` ()
|
smpTest _ test' = runSmpTest test' `shouldReturn` ()
|
||||||
|
|
||||||
smpTestN :: (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c] -> IO ()) -> Expectation
|
smpTestN :: (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO ()) -> Expectation
|
||||||
smpTestN n test' = runSmpTestN n test' `shouldReturn` ()
|
smpTestN n test' = runSmpTestN n test' `shouldReturn` ()
|
||||||
|
|
||||||
smpTest2 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> IO ()) -> Expectation
|
smpTest2 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
smpTest2 = smpTest2Cfg cfg supportedClientSMPRelayVRange
|
smpTest2 = smpTest2Cfg cfg supportedClientSMPRelayVRange
|
||||||
|
|
||||||
smpTest2Cfg :: forall c. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> IO ()) -> Expectation
|
smpTest2Cfg :: forall c. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
smpTest2Cfg srvCfg clntVR _ test' = runSmpTestNCfg srvCfg clntVR 2 _test `shouldReturn` ()
|
smpTest2Cfg srvCfg clntVR _ test' = runSmpTestNCfg srvCfg clntVR 2 _test `shouldReturn` ()
|
||||||
where
|
where
|
||||||
_test :: HasCallStack => [THandleSMP c] -> IO ()
|
_test :: HasCallStack => [THandleSMP c 'TClient] -> IO ()
|
||||||
_test [h1, h2] = test' h1 h2
|
_test [h1, h2] = test' h1 h2
|
||||||
_test _ = error "expected 2 handles"
|
_test _ = error "expected 2 handles"
|
||||||
|
|
||||||
smpTest3 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> THandleSMP c -> IO ()) -> Expectation
|
smpTest3 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
smpTest3 _ test' = smpTestN 3 _test
|
smpTest3 _ test' = smpTestN 3 _test
|
||||||
where
|
where
|
||||||
_test :: HasCallStack => [THandleSMP c] -> IO ()
|
_test :: HasCallStack => [THandleSMP c 'TClient] -> IO ()
|
||||||
_test [h1, h2, h3] = test' h1 h2 h3
|
_test [h1, h2, h3] = test' h1 h2 h3
|
||||||
_test _ = error "expected 3 handles"
|
_test _ = error "expected 3 handles"
|
||||||
|
|
||||||
smpTest4 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> THandleSMP c -> THandleSMP c -> IO ()) -> Expectation
|
smpTest4 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
smpTest4 _ test' = smpTestN 4 _test
|
smpTest4 _ test' = smpTestN 4 _test
|
||||||
where
|
where
|
||||||
_test :: HasCallStack => [THandleSMP c] -> IO ()
|
_test :: HasCallStack => [THandleSMP c 'TClient] -> IO ()
|
||||||
_test [h1, h2, h3, h4] = test' h1 h2 h3 h4
|
_test [h1, h2, h3, h4] = test' h1 h2 h3 h4
|
||||||
_test _ = error "expected 4 handles"
|
_test _ = error "expected 4 handles"
|
||||||
|
|
||||||
|
|||||||
+18
-17
@@ -78,13 +78,13 @@ pattern Ids rId sId srvDh <- IDS (QIK rId sId srvDh)
|
|||||||
pattern Msg :: MsgId -> MsgBody -> BrokerMsg
|
pattern Msg :: MsgId -> MsgBody -> BrokerMsg
|
||||||
pattern Msg msgId body <- MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body}
|
pattern Msg msgId body <- MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body}
|
||||||
|
|
||||||
sendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c -> (Maybe TransmissionAuth, ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg)
|
sendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg)
|
||||||
sendRecv h@THandle {params} (sgn, corrId, qId, cmd) = do
|
sendRecv h@THandle {params} (sgn, corrId, qId, cmd) = do
|
||||||
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
||||||
Right () <- tPut1 h (sgn, tToSend)
|
Right () <- tPut1 h (sgn, tToSend)
|
||||||
tGet1 h
|
tGet1 h
|
||||||
|
|
||||||
signSendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c -> C.APrivateAuthKey -> (ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg)
|
signSendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> (ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg)
|
||||||
signSendRecv h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
|
signSendRecv h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
|
||||||
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
||||||
Right () <- tPut1 h (authorize tForAuth, tToSend)
|
Right () <- tPut1 h (authorize tForAuth, tToSend)
|
||||||
@@ -93,17 +93,17 @@ signSendRecv h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
|
|||||||
authorize t = case a of
|
authorize t = case a of
|
||||||
C.SEd25519 -> Just . TASignature . C.ASignature C.SEd25519 $ C.sign' pk t
|
C.SEd25519 -> Just . TASignature . C.ASignature C.SEd25519 $ C.sign' pk t
|
||||||
C.SEd448 -> Just . TASignature . C.ASignature C.SEd448 $ C.sign' pk t
|
C.SEd448 -> Just . TASignature . C.ASignature C.SEd448 $ C.sign' pk t
|
||||||
C.SX25519 -> (\THandleAuth {peerPubKey} -> TAAuthenticator $ C.cbAuthenticate peerPubKey pk (C.cbNonce corrId) t) <$> thAuth params
|
C.SX25519 -> (\THAuthClient {serverPeerPubKey = k} -> TAAuthenticator $ C.cbAuthenticate k pk (C.cbNonce corrId) t) <$> thAuth params
|
||||||
#if !MIN_VERSION_base(4,18,0)
|
#if !MIN_VERSION_base(4,18,0)
|
||||||
_sx448 -> undefined -- ghc8107 fails to the branch excluded by types
|
_sx448 -> undefined -- ghc8107 fails to the branch excluded by types
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
tPut1 :: Transport c => THandle v c -> SentRawTransmission -> IO (Either TransportError ())
|
tPut1 :: Transport c => THandle v c 'TClient -> SentRawTransmission -> IO (Either TransportError ())
|
||||||
tPut1 h t = do
|
tPut1 h t = do
|
||||||
[r] <- tPut h [Right t]
|
[r] <- tPut h [Right t]
|
||||||
pure r
|
pure r
|
||||||
|
|
||||||
tGet1 :: (ProtocolEncoding v err cmd, Transport c) => THandle v c -> IO (SignedTransmission err cmd)
|
tGet1 :: (ProtocolEncoding v err cmd, Transport c) => THandle v c 'TClient -> IO (SignedTransmission err cmd)
|
||||||
tGet1 h = do
|
tGet1 h = do
|
||||||
[r] <- liftIO $ tGet h
|
[r] <- liftIO $ tGet h
|
||||||
pure r
|
pure r
|
||||||
@@ -555,12 +555,12 @@ testWithStoreLog at@(ATransport t) =
|
|||||||
logSize testStoreLogFile `shouldReturn` 1
|
logSize testStoreLogFile `shouldReturn` 1
|
||||||
removeFile testStoreLogFile
|
removeFile testStoreLogFile
|
||||||
where
|
where
|
||||||
runTest :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> ThreadId -> Expectation
|
runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
|
||||||
runTest _ test' server = do
|
runTest _ test' server = do
|
||||||
testSMPClient test' `shouldReturn` ()
|
testSMPClient test' `shouldReturn` ()
|
||||||
killThread server
|
killThread server
|
||||||
|
|
||||||
runClient :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> Expectation
|
runClient :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
||||||
|
|
||||||
logSize :: FilePath -> IO Int
|
logSize :: FilePath -> IO Int
|
||||||
@@ -653,12 +653,12 @@ testRestoreMessages at@(ATransport t) =
|
|||||||
removeFile testStoreMsgsFile
|
removeFile testStoreMsgsFile
|
||||||
removeFile testServerStatsBackupFile
|
removeFile testServerStatsBackupFile
|
||||||
where
|
where
|
||||||
runTest :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> ThreadId -> Expectation
|
runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
|
||||||
runTest _ test' server = do
|
runTest _ test' server = do
|
||||||
testSMPClient test' `shouldReturn` ()
|
testSMPClient test' `shouldReturn` ()
|
||||||
killThread server
|
killThread server
|
||||||
|
|
||||||
runClient :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> Expectation
|
runClient :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
||||||
|
|
||||||
checkStats :: ServerStatsData -> [RecipientId] -> Int -> Int -> Expectation
|
checkStats :: ServerStatsData -> [RecipientId] -> Int -> Int -> Expectation
|
||||||
@@ -727,15 +727,15 @@ testRestoreExpireMessages at@(ATransport t) =
|
|||||||
Right ServerStatsData {_msgExpired} <- strDecode <$> B.readFile testServerStatsBackupFile
|
Right ServerStatsData {_msgExpired} <- strDecode <$> B.readFile testServerStatsBackupFile
|
||||||
_msgExpired `shouldBe` 2
|
_msgExpired `shouldBe` 2
|
||||||
where
|
where
|
||||||
runTest :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> ThreadId -> Expectation
|
runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
|
||||||
runTest _ test' server = do
|
runTest _ test' server = do
|
||||||
testSMPClient test' `shouldReturn` ()
|
testSMPClient test' `shouldReturn` ()
|
||||||
killThread server
|
killThread server
|
||||||
|
|
||||||
runClient :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> Expectation
|
runClient :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> Expectation
|
||||||
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
runClient _ test' = testSMPClient test' `shouldReturn` ()
|
||||||
|
|
||||||
createAndSecureQueue :: Transport c => THandleSMP c -> SndPublicAuthKey -> IO (SenderId, RecipientId, RcvPrivateAuthKey, RcvDhSecret)
|
createAndSecureQueue :: Transport c => THandleSMP c 'TClient -> SndPublicAuthKey -> IO (SenderId, RecipientId, RcvPrivateAuthKey, RcvDhSecret)
|
||||||
createAndSecureQueue h sPub = do
|
createAndSecureQueue h sPub = do
|
||||||
g <- C.newRandom
|
g <- C.newRandom
|
||||||
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
|
||||||
@@ -759,8 +759,8 @@ testTiming (ATransport t) =
|
|||||||
timingTests :: [(C.AuthAlg, C.AuthAlg, Int)]
|
timingTests :: [(C.AuthAlg, C.AuthAlg, Int)]
|
||||||
timingTests =
|
timingTests =
|
||||||
[ (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd25519, 200), -- correct key type
|
[ (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd25519, 200), -- correct key type
|
||||||
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd448, 150),
|
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd448, 150),
|
||||||
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SX25519, 200),
|
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SX25519, 200),
|
||||||
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd25519, 200),
|
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd25519, 200),
|
||||||
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd448, 150), -- correct key type
|
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd448, 150), -- correct key type
|
||||||
(C.AuthAlg C.SEd448, C.AuthAlg C.SX25519, 200),
|
(C.AuthAlg C.SEd448, C.AuthAlg C.SX25519, 200),
|
||||||
@@ -770,7 +770,7 @@ testTiming (ATransport t) =
|
|||||||
]
|
]
|
||||||
timeRepeat n = fmap fst . timeItT . forM_ (replicate n ()) . const
|
timeRepeat n = fmap fst . timeItT . forM_ (replicate n ()) . const
|
||||||
similarTime t1 t2 = abs (t2 / t1 - 1) < 0.2 -- normally the difference between "no queue" and "wrong key" is less than 5%
|
similarTime t1 t2 = abs (t2 / t1 - 1) < 0.2 -- normally the difference between "no queue" and "wrong key" is less than 5%
|
||||||
testSameTiming :: forall c. Transport c => THandleSMP c -> THandleSMP c -> (C.AuthAlg, C.AuthAlg, Int) -> Expectation
|
testSameTiming :: forall c. Transport c => THandleSMP c 'TClient -> THandleSMP c 'TClient -> (C.AuthAlg, C.AuthAlg, Int) -> Expectation
|
||||||
testSameTiming rh sh (C.AuthAlg goodKeyAlg, C.AuthAlg badKeyAlg, n) = do
|
testSameTiming rh sh (C.AuthAlg goodKeyAlg, C.AuthAlg badKeyAlg, n) = do
|
||||||
g <- C.newRandom
|
g <- C.newRandom
|
||||||
(rPub, rKey) <- atomically $ C.generateAuthKeyPair goodKeyAlg g
|
(rPub, rKey) <- atomically $ C.generateAuthKeyPair goodKeyAlg g
|
||||||
@@ -791,10 +791,11 @@ testTiming (ATransport t) =
|
|||||||
|
|
||||||
runTimingTest sh badKey sId $ _SEND "hello"
|
runTimingTest sh badKey sId $ _SEND "hello"
|
||||||
where
|
where
|
||||||
runTimingTest :: PartyI p => THandleSMP c -> C.APrivateAuthKey -> ByteString -> Command p -> IO ()
|
runTimingTest :: PartyI p => THandleSMP c 'TClient -> C.APrivateAuthKey -> ByteString -> Command p -> IO ()
|
||||||
runTimingTest h badKey qId cmd = do
|
runTimingTest h badKey qId cmd = do
|
||||||
threadDelay 100000
|
threadDelay 100000
|
||||||
_ <- timeRepeat n $ do -- "warm up" the server
|
_ <- timeRepeat n $ do
|
||||||
|
-- "warm up" the server
|
||||||
Resp "dabc" _ (ERR AUTH) <- signSendRecv h badKey ("dabc", "1234", cmd)
|
Resp "dabc" _ (ERR AUTH) <- signSendRecv h badKey ("dabc", "1234", cmd)
|
||||||
return ()
|
return ()
|
||||||
threadDelay 100000
|
threadDelay 100000
|
||||||
|
|||||||
+2
-4
@@ -14,7 +14,6 @@ import Simplex.FileTransfer.Client
|
|||||||
import Simplex.FileTransfer.Description
|
import Simplex.FileTransfer.Description
|
||||||
import Simplex.FileTransfer.Server (runXFTPServerBlocking)
|
import Simplex.FileTransfer.Server (runXFTPServerBlocking)
|
||||||
import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defaultFileExpiration, defaultInactiveClientExpiration, supportedXFTPhandshakes)
|
import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defaultFileExpiration, defaultInactiveClientExpiration, supportedXFTPhandshakes)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
|
||||||
import Simplex.Messaging.Protocol (XFTPServer)
|
import Simplex.Messaging.Protocol (XFTPServer)
|
||||||
import Simplex.Messaging.Transport (ALPN)
|
import Simplex.Messaging.Transport (ALPN)
|
||||||
import Simplex.Messaging.Transport.Server
|
import Simplex.Messaging.Transport.Server
|
||||||
@@ -133,8 +132,7 @@ testXFTPClient :: HasCallStack => (HasCallStack => XFTPClient -> IO a) -> IO a
|
|||||||
testXFTPClient = testXFTPClientWith testXFTPClientConfig
|
testXFTPClient = testXFTPClientWith testXFTPClientConfig
|
||||||
|
|
||||||
testXFTPClientWith :: HasCallStack => XFTPClientConfig -> (HasCallStack => XFTPClient -> IO a) -> IO a
|
testXFTPClientWith :: HasCallStack => XFTPClientConfig -> (HasCallStack => XFTPClient -> IO a) -> IO a
|
||||||
testXFTPClientWith cfg client = do
|
testXFTPClientWith cfg client =
|
||||||
g <- C.newRandom
|
getXFTPClient (1, testXFTPServer, Nothing) cfg (\_ -> pure ()) >>= \case
|
||||||
getXFTPClient g (1, testXFTPServer, Nothing) cfg (\_ -> pure ()) >>= \case
|
|
||||||
Right c -> client c
|
Right c -> client c
|
||||||
Left e -> error $ show e
|
Left e -> error $ show e
|
||||||
|
|||||||
@@ -219,8 +219,7 @@ testFileChunkExpiration = withXFTPServerCfg testXFTPServerConfig {fileExpiration
|
|||||||
testInactiveClientExpiration :: Expectation
|
testInactiveClientExpiration :: Expectation
|
||||||
testInactiveClientExpiration = withXFTPServerCfg testXFTPServerConfig {inactiveClientExpiration} $ \_ -> runRight_ $ do
|
testInactiveClientExpiration = withXFTPServerCfg testXFTPServerConfig {inactiveClientExpiration} $ \_ -> runRight_ $ do
|
||||||
disconnected <- newEmptyTMVarIO
|
disconnected <- newEmptyTMVarIO
|
||||||
g <- liftIO C.newRandom
|
c <- ExceptT $ getXFTPClient (1, testXFTPServer, Nothing) testXFTPClientConfig (\_ -> atomically $ putTMVar disconnected ())
|
||||||
c <- ExceptT $ getXFTPClient g (1, testXFTPServer, Nothing) testXFTPClientConfig (\_ -> atomically $ putTMVar disconnected ())
|
|
||||||
pingXFTP c
|
pingXFTP c
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
threadDelay 100000
|
threadDelay 100000
|
||||||
|
|||||||
Reference in New Issue
Block a user