mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-09-01 18:08:36 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc36c96282 | ||
|
|
d1894574c5 | ||
|
|
d352b13b02 | ||
|
|
9c8c1f7498 | ||
|
|
5ede3855ee | ||
|
|
a945cc5786 | ||
|
|
11a68f4f15 | ||
|
|
c48277441a | ||
|
|
a40c1ba50c | ||
|
|
b404f84b40 |
@@ -49,7 +49,7 @@ jobs:
|
|||||||
run: cabal build --enable-tests
|
run: cabal build --enable-tests
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
timeout-minutes: 30
|
timeout-minutes: 40
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cabal test --test-show-details=direct
|
run: cabal test --test-show-details=direct
|
||||||
|
|
||||||
|
|||||||
@@ -186,9 +186,11 @@ xftpClientError = \case
|
|||||||
|
|
||||||
sendXFTPCommand :: forall p. FilePartyI p => XFTPClient -> C.APrivateAuthKey -> XFTPFileId -> FileCommand p -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body)
|
sendXFTPCommand :: forall p. FilePartyI p => XFTPClient -> C.APrivateAuthKey -> XFTPFileId -> FileCommand p -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body)
|
||||||
sendXFTPCommand c@XFTPClient {thParams} pKey fId cmd chunkSpec_ = do
|
sendXFTPCommand c@XFTPClient {thParams} pKey fId cmd chunkSpec_ = do
|
||||||
|
-- TODO random corrId
|
||||||
|
let corrIdUsedAsNonce = ""
|
||||||
t <-
|
t <-
|
||||||
liftEither . first PCETransportError $
|
liftEither . first PCETransportError $
|
||||||
xftpEncodeAuthTransmission thParams pKey ("", fId, FileCmd (sFileParty @p) cmd)
|
xftpEncodeAuthTransmission thParams pKey (corrIdUsedAsNonce, fId, FileCmd (sFileParty @p) cmd)
|
||||||
sendXFTPTransmission c t chunkSpec_
|
sendXFTPTransmission c t chunkSpec_
|
||||||
|
|
||||||
sendXFTPTransmission :: XFTPClient -> ByteString -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body)
|
sendXFTPTransmission :: XFTPClient -> ByteString -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import Simplex.Messaging.Protocol
|
|||||||
SndPublicAuthKey,
|
SndPublicAuthKey,
|
||||||
Transmission,
|
Transmission,
|
||||||
TransmissionForAuth (..),
|
TransmissionForAuth (..),
|
||||||
|
CorrId (..),
|
||||||
encodeTransmission,
|
encodeTransmission,
|
||||||
encodeTransmissionForAuth,
|
encodeTransmissionForAuth,
|
||||||
messageTagP,
|
messageTagP,
|
||||||
@@ -328,7 +329,7 @@ checkParty' c = case testEquality (sFileParty @p) (sFileParty @p') of
|
|||||||
xftpEncodeAuthTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion 'TClient -> 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) (C.cbNonce $ bs corrId) tForAuth
|
||||||
|
|
||||||
xftpEncodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion p -> 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
|
||||||
|
|||||||
@@ -788,7 +788,7 @@ compatibleContactUri (CRContactUri ConnReqUriData {crAgentVRange, crSmpQueues =
|
|||||||
AgentConfig {smpClientVRange, smpAgentVRange} <- asks config
|
AgentConfig {smpClientVRange, smpAgentVRange} <- asks config
|
||||||
pure $
|
pure $
|
||||||
(,)
|
(,)
|
||||||
<$> (qUri `compatibleVersion` smpClientVRange)
|
<$> (qUri `compatibleVersion` smpClientVRange)
|
||||||
<*> (crAgentVRange `compatibleVersion` smpAgentVRange pqSup)
|
<*> (crAgentVRange `compatibleVersion` smpAgentVRange pqSup)
|
||||||
|
|
||||||
versionPQSupport_ :: VersionSMPA -> Maybe CR.VersionE2E -> PQSupport
|
versionPQSupport_ :: VersionSMPA -> Maybe CR.VersionE2E -> PQSupport
|
||||||
|
|||||||
+135
-14
@@ -54,6 +54,9 @@ module Simplex.Messaging.Client
|
|||||||
suspendSMPQueue,
|
suspendSMPQueue,
|
||||||
deleteSMPQueue,
|
deleteSMPQueue,
|
||||||
deleteSMPQueues,
|
deleteSMPQueues,
|
||||||
|
createSMPProxySession,
|
||||||
|
proxySMPMessage,
|
||||||
|
forwardSMPMessage,
|
||||||
sendProtocolCommand,
|
sendProtocolCommand,
|
||||||
|
|
||||||
-- * Supporting types and client configuration
|
-- * Supporting types and client configuration
|
||||||
@@ -69,6 +72,7 @@ module Simplex.Messaging.Client
|
|||||||
chooseTransportHost,
|
chooseTransportHost,
|
||||||
proxyUsername,
|
proxyUsername,
|
||||||
temporaryClientError,
|
temporaryClientError,
|
||||||
|
smpProxyError,
|
||||||
ServerTransmission,
|
ServerTransmission,
|
||||||
ClientCommand,
|
ClientCommand,
|
||||||
|
|
||||||
@@ -98,9 +102,12 @@ import Data.List.NonEmpty (NonEmpty (..))
|
|||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Time.Clock (UTCTime (..), getCurrentTime)
|
import Data.Time.Clock (UTCTime (..), getCurrentTime)
|
||||||
|
import qualified Data.X509 as X
|
||||||
|
import qualified Data.X509.Validation as XV
|
||||||
import Network.Socket (ServiceName)
|
import Network.Socket (ServiceName)
|
||||||
import Numeric.Natural
|
import Numeric.Natural
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON)
|
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON)
|
||||||
import Simplex.Messaging.Protocol
|
import Simplex.Messaging.Protocol
|
||||||
@@ -110,7 +117,7 @@ import Simplex.Messaging.Transport
|
|||||||
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), runTransportClient)
|
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), 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, liftEitherWith, raceAny_, threadDelay')
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
import System.Timeout (timeout)
|
import System.Timeout (timeout)
|
||||||
|
|
||||||
@@ -480,6 +487,19 @@ temporaryClientError = \case
|
|||||||
_ -> False
|
_ -> False
|
||||||
{-# INLINE temporaryClientError #-}
|
{-# INLINE temporaryClientError #-}
|
||||||
|
|
||||||
|
-- TODO keep error params
|
||||||
|
smpProxyError :: SMPClientError -> ErrorType
|
||||||
|
smpProxyError = \case
|
||||||
|
PCEProtocolError _ -> PROXY PROTOCOL
|
||||||
|
PCEResponseError _ -> PROXY RESPONSE
|
||||||
|
PCEUnexpectedResponse _ -> PROXY UNEXPECTED
|
||||||
|
PCEResponseTimeout -> PROXY TIMEOUT
|
||||||
|
PCENetworkError -> PROXY NETWORK
|
||||||
|
PCEIncompatibleHost -> PROXY BAD_HOST
|
||||||
|
PCETransportError _ -> PROXY TRANSPORT
|
||||||
|
PCECryptoError _ -> INTERNAL
|
||||||
|
PCEIOError _ -> INTERNAL
|
||||||
|
|
||||||
-- | Create a new SMP queue.
|
-- | Create a new SMP queue.
|
||||||
--
|
--
|
||||||
-- https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#create-queue-command
|
-- https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#create-queue-command
|
||||||
@@ -630,6 +650,102 @@ deleteSMPQueues :: SMPClient -> NonEmpty (RcvPrivateAuthKey, RecipientId) -> IO
|
|||||||
deleteSMPQueues = okSMPCommands DEL
|
deleteSMPQueues = okSMPCommands DEL
|
||||||
{-# INLINE deleteSMPQueues #-}
|
{-# INLINE deleteSMPQueues #-}
|
||||||
|
|
||||||
|
-- TODO picture
|
||||||
|
|
||||||
|
-- send PRXY :: SMPServer -> Maybe BasicAuth -> Command Sender
|
||||||
|
-- receives PKEY :: SessionId -> X.CertificateChain -> X.SignedExact X.PubKey -> BrokerMsg
|
||||||
|
createSMPProxySession :: SMPClient -> SMPServer -> Maybe BasicAuth -> ExceptT SMPClientError IO (SessionId, VersionSMP, C.PublicKeyX25519)
|
||||||
|
createSMPProxySession c relayServ@ProtocolServer {keyHash = C.KeyHash kh} proxyAuth =
|
||||||
|
sendSMPCommand c Nothing "" (PRXY relayServ proxyAuth) >>= \case
|
||||||
|
-- XXX: rfc says sessionId should be in the entityId of response
|
||||||
|
PKEY sId vr (chain, key) -> do
|
||||||
|
case supportedClientSMPRelayVRange `compatibleVersion` vr of
|
||||||
|
Nothing -> throwE PCEIncompatibleHost -- TODO different error
|
||||||
|
Just (Compatible v) -> liftEitherWith x509Error $ (sId,v,) <$> validateRelay chain key
|
||||||
|
r -> throwE . PCEUnexpectedResponse $ bshow r
|
||||||
|
where
|
||||||
|
x509Error :: String -> SMPClientError
|
||||||
|
x509Error _msg = PCEResponseError $ error "TODO: x509 error" -- TODO different error
|
||||||
|
validateRelay :: X.CertificateChain -> X.SignedExact X.PubKey -> Either String C.PublicKeyX25519
|
||||||
|
validateRelay (X.CertificateChain cert) exact = do
|
||||||
|
serverKey <- case cert of
|
||||||
|
[leaf, ca]
|
||||||
|
| XV.Fingerprint kh == XV.getFingerprint ca X.HashSHA256 ->
|
||||||
|
C.x509ToPublic (X.certPubKey . X.signedObject $ X.getSigned leaf, []) >>= C.pubKey
|
||||||
|
_ -> throwError "bad certificate"
|
||||||
|
pubKey <- C.verifyX509 serverKey exact
|
||||||
|
C.x509ToPublic (pubKey, []) >>= C.pubKey
|
||||||
|
|
||||||
|
-- consider how to process slow responses - is it handled somehow locally or delegated to the caller
|
||||||
|
-- this method is used in the client
|
||||||
|
-- sends PFWD :: C.PublicKeyX25519 -> EncTransmission -> Command Sender
|
||||||
|
-- receives PRES :: EncResponse -> BrokerMsg -- proxy to client
|
||||||
|
proxySMPMessage ::
|
||||||
|
SMPClient ->
|
||||||
|
-- proxy session from PKEY
|
||||||
|
SessionId ->
|
||||||
|
VersionSMP ->
|
||||||
|
C.PublicKeyX25519 ->
|
||||||
|
-- message to deliver
|
||||||
|
Maybe SndPrivateAuthKey ->
|
||||||
|
SenderId ->
|
||||||
|
MsgFlags ->
|
||||||
|
MsgBody ->
|
||||||
|
ExceptT SMPClientError IO ()
|
||||||
|
-- TODO use version
|
||||||
|
proxySMPMessage c@ProtocolClient {thParams = proxyThParams, client_ = PClient {clientCorrId = g}} sessionId _v serverKey spKey sId flags msg = do
|
||||||
|
-- prepare params
|
||||||
|
let serverThAuth = (\ta -> ta {serverPeerPubKey = serverKey}) <$> thAuth proxyThParams
|
||||||
|
serverThParams = proxyThParams {sessionId, thAuth = serverThAuth}
|
||||||
|
(cmdPubKey, cmdPrivKey) <- liftIO . atomically $ C.generateKeyPair @'C.X25519 g
|
||||||
|
let cmdSecret = C.dh' serverKey cmdPrivKey
|
||||||
|
nonce@(C.CbNonce corrId) <- liftIO . atomically $ C.randomCbNonce g
|
||||||
|
-- encode
|
||||||
|
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth serverThParams (CorrId corrId, sId, Cmd SSender $ SEND flags msg)
|
||||||
|
auth <- liftEitherWith PCETransportError $ authTransmission serverThAuth spKey nonce tForAuth
|
||||||
|
b <- case batchTransmissions (batch serverThParams) (blockSize serverThParams) [Right (auth, tToSend)] of
|
||||||
|
[] -> throwE $ PCETransportError TELargeMsg -- some other error. Internal?
|
||||||
|
TBError e _ : _ -> throwE $ PCETransportError e -- large message error?
|
||||||
|
TBTransmission s _ : _ -> pure s
|
||||||
|
TBTransmissions s _ _ : _ -> pure s
|
||||||
|
et <- liftEitherWith PCECryptoError $ EncTransmission <$> C.cbEncrypt cmdSecret nonce b paddedProxiedMsgLength
|
||||||
|
sendProtocolCommand_ c (Just nonce) Nothing sessionId (Cmd SProxiedClient (PFWD cmdPubKey et)) >>= \case
|
||||||
|
-- TODO support PKEY + resend?
|
||||||
|
PRES (EncResponse er) -> do
|
||||||
|
t' <- liftEitherWith PCECryptoError $ C.cbDecrypt cmdSecret (C.reverseNonce nonce) er
|
||||||
|
case tParse proxyThParams t' of
|
||||||
|
t'' :| [] -> case tDecodeParseValidate proxyThParams t'' of
|
||||||
|
(_auth, _signed, (_c, _e, r)) -> case r of -- TODO: verify
|
||||||
|
Left e -> throwE $ PCEResponseError e
|
||||||
|
Right OK -> pure ()
|
||||||
|
Right (ERR e) -> throwE $ PCEProtocolError e
|
||||||
|
Right u -> throwE . PCEUnexpectedResponse $ bshow u -- possibly differentiate unexpected response from server/proxy
|
||||||
|
_ -> throwE $ PCETransportError TEBadBlock
|
||||||
|
r -> throwE . PCEUnexpectedResponse $ bshow r -- from proxy
|
||||||
|
|
||||||
|
-- this method is used in the proxy
|
||||||
|
-- sends RFWD :: EncFwdTransmission -> Command Sender
|
||||||
|
-- receives RRES :: EncFwdResponse -> BrokerMsg
|
||||||
|
-- proxy should send PRES to the client with EncResponse
|
||||||
|
forwardSMPMessage :: SMPClient -> CorrId -> C.PublicKeyX25519 -> EncTransmission -> ExceptT SMPClientError IO EncResponse
|
||||||
|
forwardSMPMessage c@ProtocolClient {thParams, client_ = PClient {clientCorrId = g}} fwdCorrId fwdKey fwdTransmission = do
|
||||||
|
-- prepare params
|
||||||
|
sessSecret <- case thAuth thParams of
|
||||||
|
Nothing -> throwError $ PCEProtocolError INTERNAL -- different error - proxy didn't pass key?
|
||||||
|
Just THAuthClient {serverPeerPubKey, clientPrivKey} -> pure $ C.dh' serverPeerPubKey clientPrivKey
|
||||||
|
nonce <- liftIO . atomically $ C.randomCbNonce g
|
||||||
|
-- wrap
|
||||||
|
let fwdT = FwdTransmission {fwdCorrId, fwdKey, fwdTransmission}
|
||||||
|
eft <- liftEitherWith PCECryptoError $ EncFwdTransmission <$> C.cbEncrypt sessSecret nonce (smpEncode fwdT) paddedForwardedMsgLength
|
||||||
|
-- send
|
||||||
|
sendProtocolCommand_ c (Just nonce) Nothing "" (Cmd SSender (RFWD eft)) >>= \case
|
||||||
|
RRES (EncFwdResponse efr) -> do
|
||||||
|
-- unwrap
|
||||||
|
r' <- liftEitherWith PCECryptoError $ C.cbDecrypt sessSecret (C.reverseNonce nonce) efr
|
||||||
|
FwdResponse {fwdCorrId = _, fwdResponse} <- liftEitherWith (const $ PCEResponseError BLOCK) $ smpDecode r'
|
||||||
|
pure fwdResponse
|
||||||
|
r -> throwE . PCEUnexpectedResponse $ bshow r
|
||||||
|
|
||||||
okSMPCommand :: PartyI p => Command p -> SMPClient -> C.APrivateAuthKey -> QueueId -> ExceptT SMPClientError IO ()
|
okSMPCommand :: PartyI p => Command p -> SMPClient -> C.APrivateAuthKey -> QueueId -> ExceptT SMPClientError IO ()
|
||||||
okSMPCommand cmd c pKey qId =
|
okSMPCommand cmd c pKey qId =
|
||||||
sendSMPCommand c (Just pKey) qId cmd >>= \case
|
sendSMPCommand c (Just pKey) qId cmd >>= \case
|
||||||
@@ -693,8 +809,11 @@ sendBatch c@ProtocolClient {client_ = PClient {sndQ}} b = do
|
|||||||
|
|
||||||
-- | 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
|
||||||
sendProtocolCommand c@ProtocolClient {client_ = PClient {sndQ}, thParams = THandleParams {batch, blockSize}} pKey entId cmd =
|
sendProtocolCommand c = sendProtocolCommand_ c Nothing
|
||||||
ExceptT $ uncurry sendRecv =<< mkTransmission c (pKey, entId, cmd)
|
|
||||||
|
sendProtocolCommand_ :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.CbNonce -> Maybe C.APrivateAuthKey -> EntityId -> ProtoCommand msg -> ExceptT (ProtocolClientError err) IO msg
|
||||||
|
sendProtocolCommand_ c@ProtocolClient {client_ = PClient {sndQ}, thParams = THandleParams {batch, blockSize}} nonce_ pKey entId cmd =
|
||||||
|
ExceptT $ uncurry sendRecv =<< mkTransmission_ c nonce_ (pKey, entId, cmd)
|
||||||
where
|
where
|
||||||
-- two separate "atomically" needed to avoid blocking
|
-- two separate "atomically" needed to avoid blocking
|
||||||
sendRecv :: Either TransportError SentRawTransmission -> Request err msg -> IO (Either (ProtocolClientError err) msg)
|
sendRecv :: Either TransportError SentRawTransmission -> Request err msg -> IO (Either (ProtocolClientError err) msg)
|
||||||
@@ -713,33 +832,35 @@ getResponse :: ProtocolClient v err msg -> Request err msg -> IO (Response err m
|
|||||||
getResponse ProtocolClient {client_ = PClient {tcpTimeout, pingErrorCount}} Request {entityId, responseVar} = do
|
getResponse ProtocolClient {client_ = PClient {tcpTimeout, pingErrorCount}} Request {entityId, responseVar} = do
|
||||||
response <-
|
response <-
|
||||||
timeout tcpTimeout (atomically (takeTMVar responseVar)) >>= \case
|
timeout tcpTimeout (atomically (takeTMVar responseVar)) >>= \case
|
||||||
|
-- BTW: another registerDelay candidate. Also, crashes caller with BlockedIndef.
|
||||||
Just r -> atomically (writeTVar pingErrorCount 0) $> r
|
Just r -> atomically (writeTVar pingErrorCount 0) $> r
|
||||||
Nothing -> pure $ Left PCEResponseTimeout
|
Nothing -> pure $ 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 :: ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> ClientCommand msg -> IO (PCTransmission err msg)
|
||||||
mkTransmission ProtocolClient {thParams, client_ = PClient {clientCorrId, sentCommands}} (pKey_, entId, cmd) = do
|
mkTransmission c = mkTransmission_ c Nothing
|
||||||
corrId <- atomically getNextCorrId
|
|
||||||
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, entId, cmd)
|
mkTransmission_ :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.CbNonce -> ClientCommand msg -> IO (PCTransmission err msg)
|
||||||
auth = authTransmission (thAuth thParams) pKey_ corrId tForAuth
|
mkTransmission_ ProtocolClient {thParams, client_ = PClient {clientCorrId, sentCommands}} nonce_ (pKey_, entId, cmd) = do
|
||||||
r <- atomically $ mkRequest corrId
|
nonce@(C.CbNonce corrId) <- maybe (atomically $ C.randomCbNonce clientCorrId) pure nonce_
|
||||||
|
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (CorrId corrId, entId, cmd)
|
||||||
|
auth = authTransmission (thAuth thParams) pKey_ nonce tForAuth
|
||||||
|
r <- atomically $ mkRequest (CorrId corrId)
|
||||||
pure ((,tToSend) <$> auth, r)
|
pure ((,tToSend) <$> auth, r)
|
||||||
where
|
where
|
||||||
getNextCorrId :: STM CorrId
|
|
||||||
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 entId <$> newEmptyTMVar
|
||||||
TM.insert corrId r sentCommands
|
TM.insert corrId r sentCommands
|
||||||
pure r
|
pure r
|
||||||
|
|
||||||
authTransmission :: Maybe (THandleAuth 'TClient) -> Maybe C.APrivateAuthKey -> CorrId -> ByteString -> Either TransportError (Maybe TransmissionAuth)
|
authTransmission :: Maybe (THandleAuth 'TClient) -> Maybe C.APrivateAuthKey -> C.CbNonce -> ByteString -> Either TransportError (Maybe TransmissionAuth)
|
||||||
authTransmission thAuth pKey_ (CorrId corrId) t = traverse authenticate pKey_
|
authTransmission thAuth pKey_ nonce 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 THAuthClient {serverPeerPubKey = k} -> Right $ TAAuthenticator $ C.cbAuthenticate k pk (C.cbNonce corrId) t
|
Just THAuthClient {serverPeerPubKey = k} -> Right $ TAAuthenticator $ C.cbAuthenticate k pk nonce t
|
||||||
Nothing -> Left TENoServerAuth
|
Nothing -> Left TENoServerAuth
|
||||||
C.SEd25519 -> sign pk
|
C.SEd25519 -> sign pk
|
||||||
C.SEd448 -> sign pk
|
C.SEd448 -> sign pk
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ data SMPClientAgent = SMPClientAgent
|
|||||||
agentQ :: TBQueue SMPClientAgentEvent,
|
agentQ :: TBQueue SMPClientAgentEvent,
|
||||||
randomDrg :: TVar ChaChaDRG,
|
randomDrg :: TVar ChaChaDRG,
|
||||||
smpClients :: TMap SMPServer SMPClientVar,
|
smpClients :: TMap SMPServer SMPClientVar,
|
||||||
|
smpSessions :: TMap SessionId SMPClient,
|
||||||
srvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey),
|
srvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey),
|
||||||
pendingSrvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey),
|
pendingSrvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey),
|
||||||
reconnections :: TVar [Async ()],
|
reconnections :: TVar [Async ()],
|
||||||
@@ -135,6 +136,7 @@ newSMPClientAgent agentCfg@SMPClientAgentConfig {msgQSize, agentQSize} randomDrg
|
|||||||
msgQ <- newTBQueue msgQSize
|
msgQ <- newTBQueue msgQSize
|
||||||
agentQ <- newTBQueue agentQSize
|
agentQ <- newTBQueue agentQSize
|
||||||
smpClients <- TM.empty
|
smpClients <- TM.empty
|
||||||
|
smpSessions <- TM.empty
|
||||||
srvSubs <- TM.empty
|
srvSubs <- TM.empty
|
||||||
pendingSrvSubs <- TM.empty
|
pendingSrvSubs <- TM.empty
|
||||||
reconnections <- newTVar []
|
reconnections <- newTVar []
|
||||||
@@ -147,6 +149,7 @@ newSMPClientAgent agentCfg@SMPClientAgentConfig {msgQSize, agentQSize} randomDrg
|
|||||||
agentQ,
|
agentQ,
|
||||||
randomDrg,
|
randomDrg,
|
||||||
smpClients,
|
smpClients,
|
||||||
|
smpSessions,
|
||||||
srvSubs,
|
srvSubs,
|
||||||
pendingSrvSubs,
|
pendingSrvSubs,
|
||||||
reconnections,
|
reconnections,
|
||||||
@@ -155,7 +158,7 @@ newSMPClientAgent agentCfg@SMPClientAgentConfig {msgQSize, agentQSize} randomDrg
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSMPServerClient' :: SMPClientAgent -> SMPServer -> ExceptT SMPClientError IO SMPClient
|
getSMPServerClient' :: SMPClientAgent -> SMPServer -> ExceptT SMPClientError IO SMPClient
|
||||||
getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, workerSeq} srv =
|
getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, smpSessions, msgQ, randomDrg, workerSeq} srv =
|
||||||
atomically getClientVar >>= either newSMPClient waitForSMPClient
|
atomically getClientVar >>= either newSMPClient waitForSMPClient
|
||||||
where
|
where
|
||||||
getClientVar :: STM (Either SMPClientVar SMPClientVar)
|
getClientVar :: STM (Either SMPClientVar SMPClientVar)
|
||||||
@@ -178,7 +181,9 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
|
|||||||
tryE (connectClient v) >>= \r -> case r of
|
tryE (connectClient v) >>= \r -> case r of
|
||||||
Right smp -> do
|
Right smp -> do
|
||||||
logInfo . decodeUtf8 $ "Agent connected to " <> showServer srv
|
logInfo . decodeUtf8 $ "Agent connected to " <> showServer srv
|
||||||
atomically $ putTMVar (sessionVar v) r
|
atomically $ do
|
||||||
|
putTMVar (sessionVar v) r
|
||||||
|
TM.insert (sessionId $ thParams smp) smp smpSessions
|
||||||
successAction smp
|
successAction smp
|
||||||
Left e -> do
|
Left e -> do
|
||||||
if e == PCENetworkError || e == PCEResponseTimeout
|
if e == PCENetworkError || e == PCEResponseTimeout
|
||||||
@@ -200,13 +205,14 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
|
|||||||
connectClient v = ExceptT $ getProtocolClient randomDrg (1, srv, Nothing) (smpCfg agentCfg) (Just msgQ) (clientDisconnected v)
|
connectClient v = ExceptT $ getProtocolClient randomDrg (1, srv, Nothing) (smpCfg agentCfg) (Just msgQ) (clientDisconnected v)
|
||||||
|
|
||||||
clientDisconnected :: SMPClientVar -> SMPClient -> IO ()
|
clientDisconnected :: SMPClientVar -> SMPClient -> IO ()
|
||||||
clientDisconnected v _ = do
|
clientDisconnected v smp = do
|
||||||
removeClientAndSubs v >>= (`forM_` serverDown)
|
removeClientAndSubs v smp >>= (`forM_` serverDown)
|
||||||
logInfo . decodeUtf8 $ "Agent disconnected from " <> showServer srv
|
logInfo . decodeUtf8 $ "Agent disconnected from " <> showServer srv
|
||||||
|
|
||||||
removeClientAndSubs :: SMPClientVar -> IO (Maybe (Map SMPSub C.APrivateAuthKey))
|
removeClientAndSubs :: SMPClientVar -> SMPClient -> IO (Maybe (Map SMPSub C.APrivateAuthKey))
|
||||||
removeClientAndSubs v = atomically $ do
|
removeClientAndSubs v smp = atomically $ do
|
||||||
removeSessVar v srv smpClients
|
removeSessVar v srv smpClients
|
||||||
|
TM.delete (sessionId $ thParams smp) smpSessions
|
||||||
TM.lookupDelete srv (srvSubs ca) >>= mapM updateSubs
|
TM.lookupDelete srv (srvSubs ca) >>= mapM updateSubs
|
||||||
where
|
where
|
||||||
updateSubs sVar = do
|
updateSubs sVar = do
|
||||||
@@ -271,6 +277,9 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
|
|||||||
notify :: SMPClientAgentEvent -> IO ()
|
notify :: SMPClientAgentEvent -> IO ()
|
||||||
notify evt = atomically $ writeTBQueue (agentQ ca) evt
|
notify evt = atomically $ writeTBQueue (agentQ ca) evt
|
||||||
|
|
||||||
|
lookupSMPServerClient :: SMPClientAgent -> SessionId -> STM (Maybe SMPClient)
|
||||||
|
lookupSMPServerClient SMPClientAgent {smpSessions} sessId = TM.lookup sessId smpSessions
|
||||||
|
|
||||||
closeSMPClientAgent :: SMPClientAgent -> IO ()
|
closeSMPClientAgent :: SMPClientAgent -> IO ()
|
||||||
closeSMPClientAgent c = do
|
closeSMPClientAgent c = do
|
||||||
closeSMPServerClients c
|
closeSMPServerClients c
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ module Simplex.Messaging.Crypto
|
|||||||
sbEncrypt_,
|
sbEncrypt_,
|
||||||
cbNonce,
|
cbNonce,
|
||||||
randomCbNonce,
|
randomCbNonce,
|
||||||
|
reverseNonce,
|
||||||
|
|
||||||
-- * NaCl crypto_secretbox
|
-- * NaCl crypto_secretbox
|
||||||
SbKey (unSbKey),
|
SbKey (unSbKey),
|
||||||
@@ -1292,6 +1293,9 @@ randomCbNonce = fmap CryptoBoxNonce . randomBytes 24
|
|||||||
randomBytes :: Int -> TVar ChaChaDRG -> STM ByteString
|
randomBytes :: Int -> TVar ChaChaDRG -> STM ByteString
|
||||||
randomBytes n gVar = stateTVar gVar $ randomBytesGenerate n
|
randomBytes n gVar = stateTVar gVar $ randomBytesGenerate n
|
||||||
|
|
||||||
|
reverseNonce :: CbNonce -> CbNonce
|
||||||
|
reverseNonce (CryptoBoxNonce s) = CryptoBoxNonce (B.reverse s)
|
||||||
|
|
||||||
instance Encoding CbNonce where
|
instance Encoding CbNonce where
|
||||||
smpEncode = unCbNonce
|
smpEncode = unCbNonce
|
||||||
smpP = CryptoBoxNonce <$> A.take 24
|
smpP = CryptoBoxNonce <$> A.take 24
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ module Simplex.Messaging.Protocol
|
|||||||
( -- * SMP protocol parameters
|
( -- * SMP protocol parameters
|
||||||
supportedSMPClientVRange,
|
supportedSMPClientVRange,
|
||||||
maxMessageLength,
|
maxMessageLength,
|
||||||
|
paddedProxiedMsgLength,
|
||||||
|
paddedForwardedMsgLength,
|
||||||
e2eEncConfirmationLength,
|
e2eEncConfirmationLength,
|
||||||
e2eEncMessageLength,
|
e2eEncMessageLength,
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ module Simplex.Messaging.Protocol
|
|||||||
SubscriptionMode (..),
|
SubscriptionMode (..),
|
||||||
Party (..),
|
Party (..),
|
||||||
Cmd (..),
|
Cmd (..),
|
||||||
|
DirectParty,
|
||||||
BrokerMsg (..),
|
BrokerMsg (..),
|
||||||
SParty (..),
|
SParty (..),
|
||||||
PartyI (..),
|
PartyI (..),
|
||||||
@@ -63,6 +66,7 @@ module Simplex.Messaging.Protocol
|
|||||||
ProtocolErrorType (..),
|
ProtocolErrorType (..),
|
||||||
ErrorType (..),
|
ErrorType (..),
|
||||||
CommandError (..),
|
CommandError (..),
|
||||||
|
ProxyError (..),
|
||||||
Transmission,
|
Transmission,
|
||||||
TransmissionAuth (..),
|
TransmissionAuth (..),
|
||||||
SignedTransmission,
|
SignedTransmission,
|
||||||
@@ -121,6 +125,12 @@ module Simplex.Messaging.Protocol
|
|||||||
EncNMsgMeta,
|
EncNMsgMeta,
|
||||||
SMPMsgMeta (..),
|
SMPMsgMeta (..),
|
||||||
NMsgMeta (..),
|
NMsgMeta (..),
|
||||||
|
EncFwdResponse (..),
|
||||||
|
EncFwdTransmission (..),
|
||||||
|
EncResponse (..),
|
||||||
|
EncTransmission (..),
|
||||||
|
FwdResponse (..),
|
||||||
|
FwdTransmission (..),
|
||||||
MsgFlags (..),
|
MsgFlags (..),
|
||||||
initialSMPClientVersion,
|
initialSMPClientVersion,
|
||||||
userProtocol,
|
userProtocol,
|
||||||
@@ -191,6 +201,7 @@ import Data.Word (Word16)
|
|||||||
import qualified Data.X509 as X
|
import qualified Data.X509 as X
|
||||||
import GHC.TypeLits (ErrorMessage (..), TypeError, type (+))
|
import GHC.TypeLits (ErrorMessage (..), TypeError, type (+))
|
||||||
import qualified GHC.TypeLits as TE
|
import qualified GHC.TypeLits as TE
|
||||||
|
import qualified GHC.TypeLits as Type
|
||||||
import Network.Socket (ServiceName)
|
import Network.Socket (ServiceName)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
@@ -233,6 +244,20 @@ supportedSMPClientVRange = mkVersionRange initialSMPClientVersion currentSMPClie
|
|||||||
maxMessageLength :: Int
|
maxMessageLength :: Int
|
||||||
maxMessageLength = 16088
|
maxMessageLength = 16088
|
||||||
|
|
||||||
|
-- without signature works with min 16151 (fails with 16150)
|
||||||
|
-- with Ed448: 16265 (fails with 16264)
|
||||||
|
-- with Ed25519: 16215 (fails with 16214)
|
||||||
|
-- with X25519: 16232 (fails with 16231)
|
||||||
|
paddedProxiedMsgLength :: Int
|
||||||
|
paddedProxiedMsgLength = 16232
|
||||||
|
|
||||||
|
-- without signature works with min 16239 (fails with 16238)
|
||||||
|
-- with Ed448: 16353 (fails with 16352)
|
||||||
|
-- with Ed25519: 16303 (fails with 16302)
|
||||||
|
-- with X25519: 16320 (fails with 16319)
|
||||||
|
paddedForwardedMsgLength :: Int
|
||||||
|
paddedForwardedMsgLength = 16320
|
||||||
|
|
||||||
type MaxMessageLen = 16088
|
type MaxMessageLen = 16088
|
||||||
|
|
||||||
-- 16 extra bytes: 8 for timestamp and 8 for flags (7 flags and the space, only 1 flag is currently used)
|
-- 16 extra bytes: 8 for timestamp and 8 for flags (7 flags and the space, only 1 flag is currently used)
|
||||||
@@ -246,7 +271,7 @@ e2eEncMessageLength :: Int
|
|||||||
e2eEncMessageLength = 16032
|
e2eEncMessageLength = 16032
|
||||||
|
|
||||||
-- | SMP protocol clients
|
-- | SMP protocol clients
|
||||||
data Party = Recipient | Sender | Notifier
|
data Party = Recipient | Sender | Notifier | ProxiedClient
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
-- | Singleton types for SMP protocol clients
|
-- | Singleton types for SMP protocol clients
|
||||||
@@ -254,11 +279,13 @@ data SParty :: Party -> Type where
|
|||||||
SRecipient :: SParty Recipient
|
SRecipient :: SParty Recipient
|
||||||
SSender :: SParty Sender
|
SSender :: SParty Sender
|
||||||
SNotifier :: SParty Notifier
|
SNotifier :: SParty Notifier
|
||||||
|
SProxiedClient :: SParty ProxiedClient
|
||||||
|
|
||||||
instance TestEquality SParty where
|
instance TestEquality SParty where
|
||||||
testEquality SRecipient SRecipient = Just Refl
|
testEquality SRecipient SRecipient = Just Refl
|
||||||
testEquality SSender SSender = Just Refl
|
testEquality SSender SSender = Just Refl
|
||||||
testEquality SNotifier SNotifier = Just Refl
|
testEquality SNotifier SNotifier = Just Refl
|
||||||
|
testEquality SProxiedClient SProxiedClient = Just Refl
|
||||||
testEquality _ _ = Nothing
|
testEquality _ _ = Nothing
|
||||||
|
|
||||||
deriving instance Show (SParty p)
|
deriving instance Show (SParty p)
|
||||||
@@ -271,6 +298,15 @@ instance PartyI Sender where sParty = SSender
|
|||||||
|
|
||||||
instance PartyI Notifier where sParty = SNotifier
|
instance PartyI Notifier where sParty = SNotifier
|
||||||
|
|
||||||
|
instance PartyI ProxiedClient where sParty = SProxiedClient
|
||||||
|
|
||||||
|
type family DirectParty (p :: Party) :: Constraint where
|
||||||
|
DirectParty Recipient = ()
|
||||||
|
DirectParty Sender = ()
|
||||||
|
DirectParty Notifier = ()
|
||||||
|
DirectParty p =
|
||||||
|
(Int ~ Bool, TypeError (Type.Text "Party " :<>: ShowType p :<>: Type.Text " is not direct"))
|
||||||
|
|
||||||
-- | Type for client command of any participant.
|
-- | Type for client command of any participant.
|
||||||
data Cmd = forall p. PartyI p => Cmd (SParty p) (Command p)
|
data Cmd = forall p. PartyI p => Cmd (SParty p) (Command p)
|
||||||
|
|
||||||
@@ -361,13 +397,13 @@ data Command (p :: Party) where
|
|||||||
PING :: Command Sender
|
PING :: Command Sender
|
||||||
-- SMP notification subscriber commands
|
-- SMP notification subscriber commands
|
||||||
NSUB :: Command Notifier
|
NSUB :: Command Notifier
|
||||||
PRXY :: SMPServer -> Maybe BasicAuth -> Command Sender -- request a relay server connection by URI
|
PRXY :: SMPServer -> Maybe BasicAuth -> Command ProxiedClient -- request a relay server connection by URI
|
||||||
-- Transmission to proxy:
|
-- Transmission to proxy:
|
||||||
-- - entity ID: ID of the session with relay returned in PKEY (response to PRXY)
|
-- - entity ID: ID of the session with relay returned in PKEY (response to PRXY)
|
||||||
-- - corrId: also used as a nonce to encrypt transmission to relay, corrId + 1 - from relay
|
-- - corrId: also used as a nonce to encrypt transmission to relay, corrId + 1 - from relay
|
||||||
-- - key (1st param in the command) is used to agree DH secret for this particular transmission and its response
|
-- - key (1st param in the command) is used to agree DH secret for this particular transmission and its response
|
||||||
-- Encrypted transmission should include session ID (tlsunique) from proxy-relay connection.
|
-- Encrypted transmission should include session ID (tlsunique) from proxy-relay connection.
|
||||||
PFWD :: C.PublicKeyX25519 -> EncTransmission -> Command Sender -- use CorrId as CbNonce, client to proxy
|
PFWD :: C.PublicKeyX25519 -> EncTransmission -> Command ProxiedClient -- use CorrId as CbNonce, client to proxy
|
||||||
-- Transmission forwarded to relay:
|
-- Transmission forwarded to relay:
|
||||||
-- - entity ID: empty
|
-- - entity ID: empty
|
||||||
-- - corrId: unique correlation ID between proxy and relay, also used as a nonce to encrypt forwarded transmission
|
-- - corrId: unique correlation ID between proxy and relay, also used as a nonce to encrypt forwarded transmission
|
||||||
@@ -401,11 +437,18 @@ newtype EncTransmission = EncTransmission ByteString
|
|||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
data FwdTransmission = FwdTransmission
|
data FwdTransmission = FwdTransmission
|
||||||
{ fwdCorrId :: ByteString,
|
{ fwdCorrId :: CorrId,
|
||||||
fwdKey :: C.PublicKeyX25519,
|
fwdKey :: C.PublicKeyX25519,
|
||||||
fwdTransmission :: ByteString
|
fwdTransmission :: EncTransmission
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance Encoding FwdTransmission where
|
||||||
|
smpEncode FwdTransmission {fwdCorrId = CorrId corrId, fwdKey, fwdTransmission = EncTransmission t} =
|
||||||
|
smpEncode (corrId, fwdKey, Tail t)
|
||||||
|
smpP = do
|
||||||
|
(corrId, fwdKey, Tail t) <- smpP
|
||||||
|
pure FwdTransmission {fwdCorrId = CorrId corrId, fwdKey, fwdTransmission = EncTransmission t}
|
||||||
|
|
||||||
newtype EncFwdTransmission = EncFwdTransmission ByteString
|
newtype EncFwdTransmission = EncFwdTransmission ByteString
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
@@ -419,7 +462,7 @@ data BrokerMsg where
|
|||||||
NID :: NotifierId -> RcvNtfPublicDhKey -> BrokerMsg
|
NID :: NotifierId -> RcvNtfPublicDhKey -> BrokerMsg
|
||||||
NMSG :: C.CbNonce -> EncNMsgMeta -> BrokerMsg
|
NMSG :: C.CbNonce -> EncNMsgMeta -> BrokerMsg
|
||||||
-- Should include certificate chain
|
-- Should include certificate chain
|
||||||
PKEY :: X.CertificateChain -> X.SignedExact X.PubKey -> BrokerMsg -- TLS-signed server key for proxy shared secret and initial sender key
|
PKEY :: SessionId -> VersionRangeSMP -> (X.CertificateChain, X.SignedExact X.PubKey) -> BrokerMsg -- TLS-signed server key for proxy shared secret and initial sender key
|
||||||
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
|
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
|
||||||
PRES :: EncResponse -> BrokerMsg -- proxy to client
|
PRES :: EncResponse -> BrokerMsg -- proxy to client
|
||||||
END :: BrokerMsg
|
END :: BrokerMsg
|
||||||
@@ -438,10 +481,17 @@ newtype EncFwdResponse = EncFwdResponse ByteString
|
|||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
data FwdResponse = FwdResponse
|
data FwdResponse = FwdResponse
|
||||||
{ fwdCorrId :: ByteString,
|
{ fwdCorrId :: CorrId,
|
||||||
fwdResponse :: ByteString
|
fwdResponse :: EncResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance Encoding FwdResponse where
|
||||||
|
smpEncode FwdResponse {fwdCorrId = CorrId corrId, fwdResponse = EncResponse t} =
|
||||||
|
smpEncode (corrId, Tail t)
|
||||||
|
smpP = do
|
||||||
|
(corrId, Tail t) <- smpP
|
||||||
|
pure FwdResponse {fwdCorrId = CorrId corrId, fwdResponse = EncResponse t}
|
||||||
|
|
||||||
newtype EncResponse = EncResponse ByteString
|
newtype EncResponse = EncResponse ByteString
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
@@ -607,8 +657,8 @@ data CommandTag (p :: Party) where
|
|||||||
DEL_ :: CommandTag Recipient
|
DEL_ :: CommandTag Recipient
|
||||||
SEND_ :: CommandTag Sender
|
SEND_ :: CommandTag Sender
|
||||||
PING_ :: CommandTag Sender
|
PING_ :: CommandTag Sender
|
||||||
PRXY_ :: CommandTag Sender
|
PRXY_ :: CommandTag ProxiedClient
|
||||||
PFWD_ :: CommandTag Sender
|
PFWD_ :: CommandTag ProxiedClient
|
||||||
RFWD_ :: CommandTag Sender
|
RFWD_ :: CommandTag Sender
|
||||||
NSUB_ :: CommandTag Notifier
|
NSUB_ :: CommandTag Notifier
|
||||||
|
|
||||||
@@ -672,8 +722,8 @@ instance ProtocolMsgTag CmdTag where
|
|||||||
"DEL" -> Just $ CT SRecipient DEL_
|
"DEL" -> Just $ CT SRecipient DEL_
|
||||||
"SEND" -> Just $ CT SSender SEND_
|
"SEND" -> Just $ CT SSender SEND_
|
||||||
"PING" -> Just $ CT SSender PING_
|
"PING" -> Just $ CT SSender PING_
|
||||||
"PRXY" -> Just $ CT SSender PRXY_
|
"PRXY" -> Just $ CT SProxiedClient PRXY_
|
||||||
"PFWD" -> Just $ CT SSender PFWD_
|
"PFWD" -> Just $ CT SProxiedClient PFWD_
|
||||||
"RFWD" -> Just $ CT SSender RFWD_
|
"RFWD" -> Just $ CT SSender RFWD_
|
||||||
"NSUB" -> Just $ CT SNotifier NSUB_
|
"NSUB" -> Just $ CT SNotifier NSUB_
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
@@ -1096,6 +1146,8 @@ data ErrorType
|
|||||||
SESSION
|
SESSION
|
||||||
| -- | SMP command is unknown or has invalid syntax
|
| -- | SMP command is unknown or has invalid syntax
|
||||||
CMD {cmdErr :: CommandError}
|
CMD {cmdErr :: CommandError}
|
||||||
|
| -- | error from proxied relay
|
||||||
|
PROXY {proxyErr :: ProxyError}
|
||||||
| -- | command authorization error - bad signature or non-existing SMP queue
|
| -- | command authorization error - bad signature or non-existing SMP queue
|
||||||
AUTH
|
AUTH
|
||||||
| -- | SMP queue capacity is exceeded on the server
|
| -- | SMP queue capacity is exceeded on the server
|
||||||
@@ -1115,8 +1167,12 @@ data ErrorType
|
|||||||
instance StrEncoding ErrorType where
|
instance StrEncoding ErrorType where
|
||||||
strEncode = \case
|
strEncode = \case
|
||||||
CMD e -> "CMD " <> bshow e
|
CMD e -> "CMD " <> bshow e
|
||||||
|
PROXY e -> "PROXY " <> bshow e
|
||||||
e -> bshow e
|
e -> bshow e
|
||||||
strP = "CMD " *> (CMD <$> parseRead1) <|> parseRead1
|
strP =
|
||||||
|
"CMD " *> (CMD <$> parseRead1)
|
||||||
|
<|> "PROXY " *> (PROXY <$> parseRead1)
|
||||||
|
<|> parseRead1
|
||||||
|
|
||||||
-- | SMP command error type.
|
-- | SMP command error type.
|
||||||
data CommandError
|
data CommandError
|
||||||
@@ -1134,6 +1190,22 @@ data CommandError
|
|||||||
NO_ENTITY
|
NO_ENTITY
|
||||||
deriving (Eq, Read, Show)
|
deriving (Eq, Read, Show)
|
||||||
|
|
||||||
|
-- TODO keep error params
|
||||||
|
data ProxyError
|
||||||
|
= -- | Correctly parsed SMP server ERR response.
|
||||||
|
-- This error is forwarded to the agent client as `ERR SMP err`.
|
||||||
|
PROTOCOL -- {protocolErr :: String}
|
||||||
|
| -- | Invalid server response that failed to parse.
|
||||||
|
-- Forwarded to the agent client as `ERR BROKER RESPONSE`.
|
||||||
|
RESPONSE -- {responseErr :: String}
|
||||||
|
| UNEXPECTED
|
||||||
|
| TIMEOUT
|
||||||
|
| NETWORK
|
||||||
|
| BAD_HOST
|
||||||
|
| NO_SESSION
|
||||||
|
| TRANSPORT -- {transportErr :: TransportError}
|
||||||
|
deriving (Eq, Read, Show)
|
||||||
|
|
||||||
-- | SMP transmission parser.
|
-- | SMP transmission parser.
|
||||||
transmissionP :: THandleParams v p -> Parser RawTransmission
|
transmissionP :: THandleParams v p -> Parser RawTransmission
|
||||||
transmissionP THandleParams {sessionId, implySessId} = do
|
transmissionP THandleParams {sessionId, implySessId} = do
|
||||||
@@ -1195,9 +1267,9 @@ instance PartyI p => ProtocolEncoding SMPVersion ErrorType (Command p) where
|
|||||||
SEND flags msg -> e (SEND_, ' ', flags, ' ', Tail msg)
|
SEND flags msg -> e (SEND_, ' ', flags, ' ', Tail msg)
|
||||||
PING -> e PING_
|
PING -> e PING_
|
||||||
NSUB -> e NSUB_
|
NSUB -> e NSUB_
|
||||||
PRXY host auth_ -> e (PRXY_, ' ', strEncode host, ' ', auth_)
|
PRXY host auth_ -> e (PRXY_, ' ', host, auth_)
|
||||||
PFWD {} -> error "TODO: e (PFWD_,,)"
|
PFWD pubKey (EncTransmission s) -> e (PFWD_, ' ', pubKey, Tail s)
|
||||||
RFWD {} -> error "TODO: e (RFWD_,,)"
|
RFWD (EncFwdTransmission s) -> e (RFWD_, ' ', Tail s)
|
||||||
where
|
where
|
||||||
e :: Encoding a => a -> ByteString
|
e :: Encoding a => a -> ByteString
|
||||||
e = smpEncode
|
e = smpEncode
|
||||||
@@ -1207,27 +1279,33 @@ instance PartyI p => ProtocolEncoding SMPVersion ErrorType (Command p) where
|
|||||||
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
|
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
|
||||||
{-# INLINE fromProtocolError #-}
|
{-# INLINE fromProtocolError #-}
|
||||||
|
|
||||||
checkCredentials (auth, _, queueId, _) cmd = case cmd of
|
checkCredentials (auth, _, entId, _) cmd = case cmd of
|
||||||
-- NEW must have signature but NOT queue ID
|
-- NEW must have signature but NOT queue ID
|
||||||
NEW {}
|
NEW {}
|
||||||
| isNothing auth -> Left $ CMD NO_AUTH
|
| isNothing auth -> Left $ CMD NO_AUTH
|
||||||
| not (B.null queueId) -> Left $ CMD HAS_AUTH
|
| not (B.null entId) -> Left $ CMD HAS_AUTH
|
||||||
| otherwise -> Right cmd
|
| otherwise -> Right cmd
|
||||||
-- SEND must have queue ID, signature is not always required
|
-- SEND must have queue ID, signature is not always required
|
||||||
SEND {}
|
SEND {}
|
||||||
| B.null queueId -> Left $ CMD NO_ENTITY
|
| B.null entId -> Left $ CMD NO_ENTITY
|
||||||
| otherwise -> Right cmd
|
| otherwise -> Right cmd
|
||||||
-- PING must not have queue ID or signature
|
PING -> noAuthCmd
|
||||||
PING
|
PRXY {} -> noAuthCmd
|
||||||
| isNothing auth && B.null queueId -> Right cmd
|
PFWD {}
|
||||||
| otherwise -> Left $ CMD HAS_AUTH
|
| B.null entId -> Left $ CMD NO_ENTITY
|
||||||
PRXY {}
|
| isNothing auth -> Right cmd
|
||||||
| isNothing auth && B.null queueId -> Right cmd
|
|
||||||
| otherwise -> Left $ CMD HAS_AUTH
|
| otherwise -> Left $ CMD HAS_AUTH
|
||||||
|
RFWD _ -> noAuthCmd
|
||||||
-- other client commands must have both signature and queue ID
|
-- other client commands must have both signature and queue ID
|
||||||
_
|
_
|
||||||
| isNothing auth || B.null queueId -> Left $ CMD NO_AUTH
|
| isNothing auth || B.null entId -> Left $ CMD NO_AUTH
|
||||||
| otherwise -> Right cmd
|
| otherwise -> Right cmd
|
||||||
|
where
|
||||||
|
-- command must not have entity ID (queue or session ID) or signature
|
||||||
|
noAuthCmd :: Either ErrorType (Command p)
|
||||||
|
noAuthCmd
|
||||||
|
| isNothing auth && B.null entId = Right cmd
|
||||||
|
| otherwise = Left $ CMD HAS_AUTH
|
||||||
|
|
||||||
instance ProtocolEncoding SMPVersion ErrorType Cmd where
|
instance ProtocolEncoding SMPVersion ErrorType Cmd where
|
||||||
type Tag Cmd = CmdTag
|
type Tag Cmd = CmdTag
|
||||||
@@ -1255,9 +1333,11 @@ instance ProtocolEncoding SMPVersion ErrorType Cmd where
|
|||||||
Cmd SSender <$> case tag of
|
Cmd SSender <$> case tag of
|
||||||
SEND_ -> SEND <$> _smpP <*> (unTail <$> _smpP)
|
SEND_ -> SEND <$> _smpP <*> (unTail <$> _smpP)
|
||||||
PING_ -> pure PING
|
PING_ -> pure PING
|
||||||
PFWD_ -> error "TODO: PFWD_"
|
RFWD_ -> RFWD <$> (EncFwdTransmission . unTail <$> _smpP)
|
||||||
RFWD_ -> error "TODO: RFWD_"
|
CT SProxiedClient tag ->
|
||||||
PRXY_ -> PRXY <$> (_smpP >>= either fail pure . strDecode) <*> _smpP
|
Cmd SProxiedClient <$> case tag of
|
||||||
|
PFWD_ -> PFWD <$> _smpP <*> (EncTransmission . unTail <$> smpP)
|
||||||
|
PRXY_ -> PRXY <$> _smpP <*> smpP
|
||||||
CT SNotifier NSUB_ -> pure $ Cmd SNotifier NSUB
|
CT SNotifier NSUB_ -> pure $ Cmd SNotifier NSUB
|
||||||
|
|
||||||
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
|
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
|
||||||
@@ -1273,7 +1353,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
|||||||
e (MSG_, ' ', msgId, Tail body)
|
e (MSG_, ' ', msgId, Tail body)
|
||||||
NID nId srvNtfDh -> e (NID_, ' ', nId, srvNtfDh)
|
NID nId srvNtfDh -> e (NID_, ' ', nId, srvNtfDh)
|
||||||
NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta)
|
NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta)
|
||||||
PKEY cert key -> e (PKEY_, ' ', C.encodeCertChain cert, C.SignedObject key)
|
PKEY sid vr (cert, key) -> e (PKEY_, ' ', sid, vr, C.encodeCertChain cert, C.SignedObject key)
|
||||||
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
|
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
|
||||||
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
|
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
|
||||||
END -> e END_
|
END -> e END_
|
||||||
@@ -1293,7 +1373,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
|||||||
IDS_ -> IDS <$> (QIK <$> _smpP <*> smpP <*> smpP)
|
IDS_ -> IDS <$> (QIK <$> _smpP <*> smpP <*> smpP)
|
||||||
NID_ -> NID <$> _smpP <*> smpP
|
NID_ -> NID <$> _smpP <*> smpP
|
||||||
NMSG_ -> NMSG <$> _smpP <*> smpP
|
NMSG_ -> NMSG <$> _smpP <*> smpP
|
||||||
PKEY_ -> PKEY <$> (A.space *> C.certChainP) <*> (C.getSignedExact <$> smpP)
|
PKEY_ -> PKEY <$> _smpP <*> smpP <*> ((,) <$> C.certChainP <*> (C.getSignedExact <$> smpP))
|
||||||
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
|
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
|
||||||
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
|
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
|
||||||
END_ -> pure END
|
END_ -> pure END
|
||||||
@@ -1308,19 +1388,24 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
|||||||
PEBlock -> BLOCK
|
PEBlock -> BLOCK
|
||||||
{-# INLINE fromProtocolError #-}
|
{-# INLINE fromProtocolError #-}
|
||||||
|
|
||||||
checkCredentials (_, _, queueId, _) cmd = case cmd of
|
checkCredentials (_, _, entId, _) cmd = case cmd of
|
||||||
-- IDS response should not have queue ID
|
-- IDS response should not have queue ID
|
||||||
IDS _ -> Right cmd
|
IDS _ -> Right cmd
|
||||||
-- ERR response does not always have queue ID
|
-- ERR response does not always have queue ID
|
||||||
ERR _ -> Right cmd
|
ERR _ -> Right cmd
|
||||||
-- PONG response must not have queue ID
|
-- PONG response must not have queue ID
|
||||||
PONG
|
PONG -> noEntityMsg
|
||||||
| B.null queueId -> Right cmd
|
PKEY {} -> noEntityMsg
|
||||||
| otherwise -> Left $ CMD HAS_AUTH
|
RRES _ -> noEntityMsg
|
||||||
-- other broker responses must have queue ID
|
-- other broker responses must have queue ID
|
||||||
_
|
_
|
||||||
| B.null queueId -> Left $ CMD NO_ENTITY
|
| B.null entId -> Left $ CMD NO_ENTITY
|
||||||
| otherwise -> Right cmd
|
| otherwise -> Right cmd
|
||||||
|
where
|
||||||
|
noEntityMsg :: Either ErrorType BrokerMsg
|
||||||
|
noEntityMsg
|
||||||
|
| B.null entId = Right cmd
|
||||||
|
| otherwise = Left $ CMD HAS_AUTH
|
||||||
|
|
||||||
-- | Parse SMP protocol commands and broker messages
|
-- | Parse SMP protocol commands and broker messages
|
||||||
parseProtocol :: forall v err msg. ProtocolEncoding v err msg => Version v -> ByteString -> Either err msg
|
parseProtocol :: forall v err msg. ProtocolEncoding v err msg => Version v -> ByteString -> Either err msg
|
||||||
@@ -1343,6 +1428,7 @@ instance Encoding ErrorType where
|
|||||||
BLOCK -> "BLOCK"
|
BLOCK -> "BLOCK"
|
||||||
SESSION -> "SESSION"
|
SESSION -> "SESSION"
|
||||||
CMD err -> "CMD " <> smpEncode err
|
CMD err -> "CMD " <> smpEncode err
|
||||||
|
PROXY err -> "PROXY " <> smpEncode err
|
||||||
AUTH -> "AUTH"
|
AUTH -> "AUTH"
|
||||||
QUOTA -> "QUOTA"
|
QUOTA -> "QUOTA"
|
||||||
EXPIRED -> "EXPIRED"
|
EXPIRED -> "EXPIRED"
|
||||||
@@ -1356,6 +1442,7 @@ instance Encoding ErrorType where
|
|||||||
"BLOCK" -> pure BLOCK
|
"BLOCK" -> pure BLOCK
|
||||||
"SESSION" -> pure SESSION
|
"SESSION" -> pure SESSION
|
||||||
"CMD" -> CMD <$> _smpP
|
"CMD" -> CMD <$> _smpP
|
||||||
|
"PROXY" -> PROXY <$> _smpP
|
||||||
"AUTH" -> pure AUTH
|
"AUTH" -> pure AUTH
|
||||||
"QUOTA" -> pure QUOTA
|
"QUOTA" -> pure QUOTA
|
||||||
"EXPIRED" -> pure EXPIRED
|
"EXPIRED" -> pure EXPIRED
|
||||||
@@ -1381,7 +1468,29 @@ instance Encoding CommandError where
|
|||||||
"NO_AUTH" -> pure NO_AUTH
|
"NO_AUTH" -> pure NO_AUTH
|
||||||
"HAS_AUTH" -> pure HAS_AUTH
|
"HAS_AUTH" -> pure HAS_AUTH
|
||||||
"NO_ENTITY" -> pure NO_ENTITY
|
"NO_ENTITY" -> pure NO_ENTITY
|
||||||
"NO_QUEUE" -> pure NO_ENTITY
|
"NO_QUEUE" -> pure NO_ENTITY -- for backward compatibility
|
||||||
|
_ -> fail "bad command error type"
|
||||||
|
|
||||||
|
instance Encoding ProxyError where
|
||||||
|
smpEncode e = case e of
|
||||||
|
PROTOCOL -> "PROTOCOL"
|
||||||
|
RESPONSE -> "RESPONSE"
|
||||||
|
UNEXPECTED -> "UNEXPECTED"
|
||||||
|
TIMEOUT -> "TIMEOUT"
|
||||||
|
NETWORK -> "NETWORK"
|
||||||
|
BAD_HOST -> "BAD_HOST"
|
||||||
|
NO_SESSION -> "NO_SESSION"
|
||||||
|
TRANSPORT -> "TRANSPORT"
|
||||||
|
smpP =
|
||||||
|
A.takeTill (== ' ') >>= \case
|
||||||
|
"PROTOCOL" -> pure PROTOCOL
|
||||||
|
"RESPONSE" -> pure RESPONSE
|
||||||
|
"UNEXPECTED" -> pure UNEXPECTED
|
||||||
|
"TIMEOUT" -> pure TIMEOUT
|
||||||
|
"NETWORK" -> pure NETWORK
|
||||||
|
"BAD_HOST" -> pure BAD_HOST
|
||||||
|
"NO_SESSION" -> pure NO_SESSION
|
||||||
|
"TRANSPORT" -> pure TRANSPORT
|
||||||
_ -> fail "bad command error type"
|
_ -> fail "bad command error type"
|
||||||
|
|
||||||
-- | Send signed SMP transmission to TCP transport.
|
-- | Send signed SMP transmission to TCP transport.
|
||||||
@@ -1521,4 +1630,6 @@ $(J.deriveJSON defaultJSON ''MsgFlags)
|
|||||||
|
|
||||||
$(J.deriveJSON (sumTypeJSON id) ''CommandError)
|
$(J.deriveJSON (sumTypeJSON id) ''CommandError)
|
||||||
|
|
||||||
|
$(J.deriveJSON (sumTypeJSON id) ''ProxyError)
|
||||||
|
|
||||||
$(J.deriveJSON (sumTypeJSON id) ''ErrorType)
|
$(J.deriveJSON (sumTypeJSON id) ''ErrorType)
|
||||||
|
|||||||
+133
-52
@@ -13,7 +13,6 @@
|
|||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- Module : Simplex.Messaging.Server
|
-- Module : Simplex.Messaging.Server
|
||||||
@@ -43,6 +42,7 @@ import Control.Monad
|
|||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
import Control.Monad.IO.Unlift
|
import Control.Monad.IO.Unlift
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
import Crypto.Random
|
import Crypto.Random
|
||||||
import Data.Bifunctor (first)
|
import Data.Bifunctor (first)
|
||||||
import Data.ByteString.Base64 (encode)
|
import Data.ByteString.Base64 (encode)
|
||||||
@@ -54,6 +54,7 @@ import Data.Functor (($>))
|
|||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import qualified Data.IntMap.Strict as IM
|
import qualified Data.IntMap.Strict as IM
|
||||||
import Data.List (intercalate)
|
import Data.List (intercalate)
|
||||||
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import Data.Maybe (isNothing)
|
import Data.Maybe (isNothing)
|
||||||
@@ -67,8 +68,10 @@ import GHC.Stats (getRTSStats)
|
|||||||
import GHC.TypeLits (KnownNat)
|
import GHC.TypeLits (KnownNat)
|
||||||
import Network.Socket (ServiceName, Socket, socketToHandle)
|
import Network.Socket (ServiceName, Socket, socketToHandle)
|
||||||
import Simplex.Messaging.Agent.Lock
|
import Simplex.Messaging.Agent.Lock
|
||||||
|
import Simplex.Messaging.Client (ProtocolClient (thParams), forwardSMPMessage, smpProxyError)
|
||||||
|
import Simplex.Messaging.Client.Agent (SMPClientAgent (..), SMPClientAgentEvent (..), getSMPServerClient', lookupSMPServerClient)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding (Encoding (smpEncode))
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Protocol
|
import Simplex.Messaging.Protocol
|
||||||
import Simplex.Messaging.Server.Control
|
import Simplex.Messaging.Server.Control
|
||||||
@@ -90,6 +93,7 @@ import System.Exit (exitFailure)
|
|||||||
import System.IO (hPrint, hPutStrLn, hSetNewlineMode, universalNewlineMode)
|
import System.IO (hPrint, hPutStrLn, hSetNewlineMode, universalNewlineMode)
|
||||||
import System.Mem.Weak (deRefWeak)
|
import System.Mem.Weak (deRefWeak)
|
||||||
import UnliftIO (timeout)
|
import UnliftIO (timeout)
|
||||||
|
import UnliftIO.Async (mapConcurrently)
|
||||||
import UnliftIO.Concurrent
|
import UnliftIO.Concurrent
|
||||||
import UnliftIO.Directory (doesFileExist, renameFile)
|
import UnliftIO.Directory (doesFileExist, renameFile)
|
||||||
import UnliftIO.Exception
|
import UnliftIO.Exception
|
||||||
@@ -122,11 +126,13 @@ type M a = ReaderT Env IO a
|
|||||||
smpServer :: TMVar Bool -> ServerConfig -> M ()
|
smpServer :: TMVar Bool -> ServerConfig -> M ()
|
||||||
smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
||||||
s <- asks server
|
s <- asks server
|
||||||
|
pa <- asks proxyAgent
|
||||||
expired <- restoreServerMessages
|
expired <- restoreServerMessages
|
||||||
restoreServerStats expired
|
restoreServerStats expired
|
||||||
raceAny_
|
raceAny_
|
||||||
( serverThread s "server subscribedQ" subscribedQ subscribers subscriptions cancelSub
|
( serverThread s "server subscribedQ" subscribedQ subscribers subscriptions cancelSub
|
||||||
: serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers ntfSubscriptions (\_ -> pure ())
|
: serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers ntfSubscriptions (\_ -> pure ())
|
||||||
|
: receiveFromProxyAgent pa
|
||||||
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
|
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
|
||||||
)
|
)
|
||||||
`finally` withLock' (savingLock s) "final" (saveServer False)
|
`finally` withLock' (savingLock s) "final" (saveServer False)
|
||||||
@@ -179,6 +185,19 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
mkWeakThreadId t >>= atomically . modifyTVar' (endThreads c) . IM.insert tId
|
mkWeakThreadId t >>= atomically . modifyTVar' (endThreads c) . IM.insert tId
|
||||||
atomically $ TM.lookupDelete qId (clientSubs c)
|
atomically $ TM.lookupDelete qId (clientSubs c)
|
||||||
|
|
||||||
|
receiveFromProxyAgent :: ProxyAgent -> M ()
|
||||||
|
receiveFromProxyAgent ProxyAgent {smpAgent = SMPClientAgent {agentQ}} =
|
||||||
|
forever $
|
||||||
|
atomically (readTBQueue agentQ) >>= \case
|
||||||
|
CAConnected srv -> logInfo $ "SMP server connected " <> showServer' srv
|
||||||
|
CADisconnected srv [] -> logInfo $ "SMP server disconnected " <> showServer' srv
|
||||||
|
CADisconnected srv subs -> logError $ "SMP server disconnected " <> showServer' srv <> " / subscriptions: " <> tshow (length subs)
|
||||||
|
CAReconnected srv -> logInfo $ "SMP server reconnected " <> showServer' srv
|
||||||
|
CAResubscribed srv subs -> logError $ "SMP server resubscribed " <> showServer' srv <> " / subscriptions: " <> tshow (length subs)
|
||||||
|
CASubError srv errs -> logError $ "SMP server subscription errors " <> showServer' srv <> " / errors: " <> tshow (length errs)
|
||||||
|
where
|
||||||
|
showServer' = decodeLatin1 . strEncode . host
|
||||||
|
|
||||||
expireMessagesThread_ :: ServerConfig -> [M ()]
|
expireMessagesThread_ :: ServerConfig -> [M ()]
|
||||||
expireMessagesThread_ ServerConfig {messageExpiration = Just msgExp} = [expireMessages msgExp]
|
expireMessagesThread_ ServerConfig {messageExpiration = Just msgExp} = [expireMessages msgExp]
|
||||||
expireMessagesThread_ _ = []
|
expireMessagesThread_ _ = []
|
||||||
@@ -314,7 +333,7 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
CPResume -> withAdminRole $ hPutStrLn h "resume not implemented"
|
CPResume -> withAdminRole $ hPutStrLn h "resume not implemented"
|
||||||
CPClients -> withAdminRole $ do
|
CPClients -> withAdminRole $ do
|
||||||
active <- unliftIO u (asks clients) >>= readTVarIO
|
active <- unliftIO u (asks clients) >>= readTVarIO
|
||||||
hPutStrLn h $ "clientId,sessionId,connected,createdAt,rcvActiveAt,sndActiveAt,age,subscriptions"
|
hPutStrLn h "clientId,sessionId,connected,createdAt,rcvActiveAt,sndActiveAt,age,subscriptions"
|
||||||
forM_ (IM.toList active) $ \(cid, Client {sessionId, connected, createdAt, rcvActiveAt, sndActiveAt, subscriptions}) -> do
|
forM_ (IM.toList active) $ \(cid, Client {sessionId, connected, createdAt, rcvActiveAt, sndActiveAt, subscriptions}) -> do
|
||||||
connected' <- bshow <$> readTVarIO connected
|
connected' <- bshow <$> readTVarIO connected
|
||||||
rcvActiveAt' <- strEncode <$> readTVarIO rcvActiveAt
|
rcvActiveAt' <- strEncode <$> readTVarIO rcvActiveAt
|
||||||
@@ -410,7 +429,7 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
hPutStrLn h "AUTH"
|
hPutStrLn h "AUTH"
|
||||||
|
|
||||||
runClientTransport :: Transport c => THandleSMP c 'TServer -> M ()
|
runClientTransport :: Transport c => THandleSMP c 'TServer -> M ()
|
||||||
runClientTransport th@THandle {params = THandleParams {thVersion, sessionId}} = do
|
runClientTransport th@THandle {params = thParams@THandleParams {thVersion, sessionId}} = do
|
||||||
q <- asks $ tbqSize . config
|
q <- asks $ tbqSize . config
|
||||||
ts <- liftIO getSystemTime
|
ts <- liftIO getSystemTime
|
||||||
active <- asks clients
|
active <- asks clients
|
||||||
@@ -422,7 +441,7 @@ runClientTransport th@THandle {params = THandleParams {thVersion, sessionId}} =
|
|||||||
s <- asks server
|
s <- asks server
|
||||||
expCfg <- asks $ inactiveClientExpiration . config
|
expCfg <- asks $ inactiveClientExpiration . config
|
||||||
labelMyThread . B.unpack $ "client $" <> encode sessionId
|
labelMyThread . B.unpack $ "client $" <> encode sessionId
|
||||||
raceAny_ ([liftIO $ send th c, client c s, receive th c] <> disconnectThread_ c expCfg)
|
raceAny_ ([liftIO $ send th c, client thParams c s, receive th c] <> disconnectThread_ c expCfg)
|
||||||
`finally` clientDisconnected c
|
`finally` clientDisconnected c
|
||||||
where
|
where
|
||||||
disconnectThread_ c (Just expCfg) = [liftIO $ disconnectTransport th (rcvActiveAt c) (sndActiveAt c) expCfg (noSubscriptions c)]
|
disconnectThread_ c (Just expCfg) = [liftIO $ disconnectTransport th (rcvActiveAt c) (sndActiveAt c) expCfg (noSubscriptions c)]
|
||||||
@@ -463,19 +482,19 @@ receive th@THandle {params = THandleParams {thAuth}} Client {rcvQ, sndQ, rcvActi
|
|||||||
forever $ do
|
forever $ do
|
||||||
ts <- L.toList <$> liftIO (tGet th)
|
ts <- L.toList <$> liftIO (tGet th)
|
||||||
atomically . writeTVar rcvActiveAt =<< liftIO getSystemTime
|
atomically . writeTVar rcvActiveAt =<< liftIO getSystemTime
|
||||||
as <- partitionEithers <$> mapM cmdAction ts
|
(errs, cmds) <- partitionEithers <$> mapM cmdAction ts
|
||||||
write sndQ $ fst as
|
write sndQ errs
|
||||||
write rcvQ $ snd as
|
write rcvQ cmds
|
||||||
where
|
where
|
||||||
cmdAction :: SignedTransmission ErrorType Cmd -> M (Either (Transmission BrokerMsg) (Maybe QueueRec, Transmission Cmd))
|
cmdAction :: SignedTransmission ErrorType Cmd -> M (Either (Transmission BrokerMsg) (Maybe QueueRec, Transmission Cmd))
|
||||||
cmdAction (tAuth, authorized, (corrId, queueId, cmdOrError)) =
|
cmdAction (tAuth, authorized, (corrId, entId, cmdOrError)) =
|
||||||
case cmdOrError of
|
case cmdOrError of
|
||||||
Left e -> pure $ Left (corrId, queueId, ERR e)
|
Left e -> pure $ Left (corrId, entId, ERR e)
|
||||||
Right cmd -> verified <$> verifyTransmission ((,C.cbNonce (bs corrId)) <$> thAuth) tAuth authorized queueId cmd
|
Right cmd -> verified <$> verifyTransmission ((,C.cbNonce (bs corrId)) <$> thAuth) tAuth authorized entId cmd
|
||||||
where
|
where
|
||||||
verified = \case
|
verified = \case
|
||||||
VRVerified qr -> Right (qr, (corrId, queueId, cmd))
|
VRVerified qr -> Right (qr, (corrId, entId, cmd))
|
||||||
VRFailed -> Left (corrId, queueId, ERR AUTH)
|
VRFailed -> Left (corrId, entId, ERR AUTH)
|
||||||
write q = mapM_ (atomically . writeTBQueue q) . L.nonEmpty
|
write q = mapM_ (atomically . writeTBQueue q) . L.nonEmpty
|
||||||
|
|
||||||
send :: Transport c => THandleSMP c 'TServer -> Client -> IO ()
|
send :: Transport c => THandleSMP c 'TServer -> Client -> IO ()
|
||||||
@@ -522,19 +541,18 @@ verifyTransmission auth_ tAuth authorized queueId cmd =
|
|||||||
-- SEND will be accepted without authorization before the queue is secured with KEY command
|
-- SEND will be accepted without authorization before the queue is secured with KEY command
|
||||||
Cmd SSender SEND {} -> verifyQueue (\q -> Just q `verified` maybe (isNothing tAuth) verify (senderKey q)) <$> get SSender
|
Cmd SSender SEND {} -> verifyQueue (\q -> Just q `verified` maybe (isNothing tAuth) verify (senderKey q)) <$> get SSender
|
||||||
Cmd SSender PING -> pure $ VRVerified Nothing
|
Cmd SSender PING -> pure $ VRVerified Nothing
|
||||||
-- NSUB will not be accepted without authorization
|
|
||||||
Cmd SNotifier NSUB -> verifyQueue (\q -> maybe dummyVerify (Just q `verifiedWith`) (notifierKey <$> notifier q)) <$> get SNotifier
|
|
||||||
Cmd SSender PRXY {} -> pure $ VRVerified Nothing
|
|
||||||
Cmd SSender PFWD {} -> pure $ VRVerified Nothing
|
|
||||||
Cmd SSender RFWD {} -> pure $ VRVerified Nothing
|
Cmd SSender RFWD {} -> pure $ VRVerified Nothing
|
||||||
|
-- NSUB will not be accepted without authorization
|
||||||
|
Cmd SNotifier NSUB -> verifyQueue (\q -> maybe dummyVerify (\n -> Just q `verifiedWith` notifierKey n) (notifier q)) <$> get SNotifier
|
||||||
|
Cmd SProxiedClient _ -> pure $ VRVerified Nothing
|
||||||
where
|
where
|
||||||
verify = verifyCmdAuthorization auth_ tAuth authorized
|
verify = verifyCmdAuthorization auth_ tAuth authorized
|
||||||
dummyVerify = verify (dummyAuthKey tAuth) `seq` VRFailed
|
dummyVerify = verify (dummyAuthKey tAuth) `seq` VRFailed
|
||||||
verifyQueue :: (QueueRec -> VerificationResult) -> Either ErrorType QueueRec -> VerificationResult
|
verifyQueue :: (QueueRec -> VerificationResult) -> Either ErrorType QueueRec -> VerificationResult
|
||||||
verifyQueue = either (\_ -> dummyVerify)
|
verifyQueue = either (const dummyVerify)
|
||||||
verified q cond = if cond then VRVerified q else VRFailed
|
verified q cond = if cond then VRVerified q else VRFailed
|
||||||
verifiedWith q k = q `verified` verify k
|
verifiedWith q k = q `verified` verify k
|
||||||
get :: SParty p -> M (Either ErrorType QueueRec)
|
get :: DirectParty p => SParty p -> M (Either ErrorType QueueRec)
|
||||||
get party = do
|
get party = do
|
||||||
st <- asks queueStore
|
st <- asks queueStore
|
||||||
atomically $ getQueue st party queueId
|
atomically $ getQueue st party queueId
|
||||||
@@ -584,36 +602,55 @@ dummyKeyEd448 = "MEMwBQYDK2VxAzoA6ibQc9XpkSLtwrf7PLvp81qW/etiumckVFImCMRdftcG/Xo
|
|||||||
dummyKeyX25519 :: C.PublicKey 'C.X25519
|
dummyKeyX25519 :: C.PublicKey 'C.X25519
|
||||||
dummyKeyX25519 = "MCowBQYDK2VuAyEA4JGSMYht18H4mas/jHeBwfcM7jLwNYJNOAhi2/g4RXg="
|
dummyKeyX25519 = "MCowBQYDK2VuAyEA4JGSMYht18H4mas/jHeBwfcM7jLwNYJNOAhi2/g4RXg="
|
||||||
|
|
||||||
client :: Client -> Server -> M ()
|
client :: THandleParams SMPVersion 'TServer -> Client -> Server -> M ()
|
||||||
client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Server {subscribedQ, ntfSubscribedQ, notifiers} = do
|
client thParams' clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Server {subscribedQ, ntfSubscribedQ, notifiers} = do
|
||||||
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
|
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
|
||||||
forever $
|
forever $ do
|
||||||
atomically (readTBQueue rcvQ)
|
(proxied, rs) <- partitionEithers . L.toList <$> (mapM processCommand =<< atomically (readTBQueue rcvQ))
|
||||||
>>= mapM processCommand
|
forM_ (L.nonEmpty rs) reply
|
||||||
>>= atomically . writeTBQueue sndQ
|
-- TODO cancel this thread if the client gets disconnected
|
||||||
|
-- TODO limit client concurrency
|
||||||
|
forM_ (L.nonEmpty proxied) $ \cmds -> forkIO $ mapConcurrently processProxiedCmd cmds >>= reply
|
||||||
where
|
where
|
||||||
processCommand :: (Maybe QueueRec, Transmission Cmd) -> M (Transmission BrokerMsg)
|
reply :: MonadIO m => NonEmpty (Transmission BrokerMsg) -> m ()
|
||||||
|
reply = atomically . writeTBQueue sndQ
|
||||||
|
processProxiedCmd :: Transmission (Command 'ProxiedClient) -> M (Transmission BrokerMsg)
|
||||||
|
processProxiedCmd (corrId, sessId, command) = (corrId, sessId,) <$> case command of
|
||||||
|
PRXY srv auth -> ifM allowProxy getRelay (pure $ ERR AUTH)
|
||||||
|
where
|
||||||
|
allowProxy = do
|
||||||
|
ServerConfig {allowSMPProxy, newQueueBasicAuth} <- asks config
|
||||||
|
pure $ allowSMPProxy && maybe True ((== auth) . Just) newQueueBasicAuth
|
||||||
|
getRelay = do
|
||||||
|
ProxyAgent {smpAgent} <- asks proxyAgent
|
||||||
|
-- TODO catch IO errors too
|
||||||
|
liftIO $ proxyResp <$> runExceptT (getSMPServerClient' smpAgent srv)
|
||||||
|
where
|
||||||
|
proxyResp = \case
|
||||||
|
Right smp ->
|
||||||
|
let THandleParams {sessionId = srvSessId, thAuth} = thParams smp
|
||||||
|
vr = supportedServerSMPRelayVRange
|
||||||
|
in case thAuth of
|
||||||
|
Just THAuthClient {serverCertKey} -> PKEY srvSessId vr serverCertKey
|
||||||
|
Nothing -> ERR $ PROXY TRANSPORT -- TODO different error?
|
||||||
|
Left err -> ERR $ smpProxyError err
|
||||||
|
PFWD pubKey encBlock -> do
|
||||||
|
ProxyAgent {smpAgent} <- asks proxyAgent
|
||||||
|
atomically (lookupSMPServerClient smpAgent sessId) >>= \case
|
||||||
|
Just smp -> liftIO $ either (ERR . smpProxyError) PRES <$> runExceptT (forwardSMPMessage smp corrId pubKey encBlock)
|
||||||
|
Nothing -> pure $ ERR $ PROXY NO_SESSION
|
||||||
|
processCommand :: (Maybe QueueRec, Transmission Cmd) -> M (Either (Transmission (Command 'ProxiedClient)) (Transmission BrokerMsg))
|
||||||
processCommand (qr_, (corrId, queueId, cmd)) = do
|
processCommand (qr_, (corrId, queueId, cmd)) = do
|
||||||
st <- asks queueStore
|
st <- asks queueStore
|
||||||
case cmd of
|
case cmd of
|
||||||
Cmd SSender command ->
|
Cmd SProxiedClient command -> pure $ Left (corrId, queueId, command)
|
||||||
case command of
|
Cmd SSender command -> Right <$> case command of
|
||||||
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
|
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
|
||||||
PING -> pure (corrId, "", PONG)
|
PING -> pure (corrId, "", PONG)
|
||||||
PRXY relay auth ->
|
RFWD encBlock -> (corrId, "",) <$> processForwardedCommand encBlock
|
||||||
ifM
|
Cmd SNotifier NSUB -> Right <$> subscribeNotifications
|
||||||
allowProxy
|
|
||||||
(setupProxy relay)
|
|
||||||
(pure (corrId, queueId, ERR AUTH))
|
|
||||||
where
|
|
||||||
allowProxy = do
|
|
||||||
ServerConfig {allowSMPProxy, newQueueBasicAuth} <- asks config
|
|
||||||
pure $ allowSMPProxy && maybe True ((== auth) . Just) newQueueBasicAuth
|
|
||||||
PFWD _dhPub _encBlock -> error "TODO: processCommand.PFWD"
|
|
||||||
RFWD _encBlock -> error "TODO: processCommand.RFWD"
|
|
||||||
Cmd SNotifier NSUB -> subscribeNotifications
|
|
||||||
Cmd SRecipient command ->
|
Cmd SRecipient command ->
|
||||||
case command of
|
Right <$> case command of
|
||||||
NEW rKey dhKey auth subMode ->
|
NEW rKey dhKey auth subMode ->
|
||||||
ifM
|
ifM
|
||||||
allowNew
|
allowNew
|
||||||
@@ -877,6 +914,59 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Serv
|
|||||||
encNMsgMeta = C.cbEncrypt rcvNtfDhSecret cbNonce (smpEncode msgMeta) 128
|
encNMsgMeta = C.cbEncrypt rcvNtfDhSecret cbNonce (smpEncode msgMeta) 128
|
||||||
pure . (cbNonce,) $ fromRight "" encNMsgMeta
|
pure . (cbNonce,) $ fromRight "" encNMsgMeta
|
||||||
|
|
||||||
|
processForwardedCommand :: EncFwdTransmission -> M BrokerMsg
|
||||||
|
processForwardedCommand (EncFwdTransmission s) = fmap (either id id) . runExceptT $ do
|
||||||
|
-- TODO error
|
||||||
|
THAuthServer {clientPeerPubKey, serverPrivKey} <- maybe (throwError $ ERR INTERNAL) pure thAuth
|
||||||
|
-- TODO compute during handshake?
|
||||||
|
let sessSecret = C.dh' clientPeerPubKey serverPrivKey
|
||||||
|
proxyNonce = C.cbNonce $ bs corrId
|
||||||
|
-- TODO error
|
||||||
|
s' <- liftEitherWith internalErr $ C.cbDecrypt sessSecret proxyNonce s
|
||||||
|
-- TODO error
|
||||||
|
FwdTransmission {fwdCorrId, fwdKey, fwdTransmission = EncTransmission et} <- liftEitherWith internalErr $ smpDecode s'
|
||||||
|
-- TODO error - this error is reported to proxy, as we failed to get to client's transmission
|
||||||
|
let clientSecret = C.dh' fwdKey serverPrivKey
|
||||||
|
clientNonce = C.cbNonce $ bs fwdCorrId
|
||||||
|
b <- liftEitherWith internalErr $ C.cbDecrypt clientSecret clientNonce et
|
||||||
|
-- only allowing single forwarded transactions
|
||||||
|
let t' = tDecodeParseValidate thParams' $ L.head $ tParse thParams' b
|
||||||
|
clntThAuth = Just $ THAuthServer {clientPeerPubKey = fwdKey, serverPrivKey}
|
||||||
|
-- TODO error
|
||||||
|
r <-
|
||||||
|
lift (rejectOrVerify clntThAuth t') >>= \case
|
||||||
|
Left r -> pure r
|
||||||
|
Right t''@(_, (corrId', entId', _)) ->
|
||||||
|
-- Left will not be returned by processCommand, as only SEND command is allowed
|
||||||
|
fromRight (corrId', entId', ERR INTERNAL) <$> lift (processCommand t'')
|
||||||
|
|
||||||
|
-- encode response
|
||||||
|
r' <- case batchTransmissions (batch thParams') (blockSize thParams') [Right (Nothing, encodeTransmission thParams' r)] of
|
||||||
|
[] -> throwE $ ERR INTERNAL -- TODO error
|
||||||
|
TBError _ _ : _ -> throwE $ ERR INTERNAL -- TODO error
|
||||||
|
TBTransmission b' _ : _ -> pure b'
|
||||||
|
TBTransmissions b' _ _ : _ -> pure b'
|
||||||
|
-- encrypt to client
|
||||||
|
r2 <- liftEitherWith internalErr $ EncResponse <$> C.cbEncrypt clientSecret (C.reverseNonce clientNonce) r' paddedProxiedMsgLength
|
||||||
|
-- encrypt to proxy
|
||||||
|
let fr = FwdResponse {fwdCorrId, fwdResponse = r2}
|
||||||
|
r3 <- liftEitherWith internalErr $ EncFwdResponse <$> C.cbEncrypt sessSecret (C.reverseNonce proxyNonce) (smpEncode fr) paddedForwardedMsgLength
|
||||||
|
pure $ RRES r3
|
||||||
|
where
|
||||||
|
internalErr _ = ERR INTERNAL -- TODO errors
|
||||||
|
THandleParams {thAuth} = thParams'
|
||||||
|
rejectOrVerify :: Maybe (THandleAuth 'TServer) -> SignedTransmission ErrorType Cmd -> M (Either (Transmission BrokerMsg) (Maybe QueueRec, Transmission Cmd))
|
||||||
|
rejectOrVerify clntThAuth (tAuth, authorized, (corrId', entId', cmdOrError)) =
|
||||||
|
case cmdOrError of
|
||||||
|
Left e -> pure $ Left (corrId', entId', ERR e)
|
||||||
|
-- flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
|
||||||
|
Right cmd'@(Cmd SSender SEND {}) -> verified <$> verifyTransmission ((,C.cbNonce (bs corrId')) <$> clntThAuth) tAuth authorized entId' cmd'
|
||||||
|
where
|
||||||
|
verified = \case
|
||||||
|
VRVerified qr -> Right (qr, (corrId', entId', cmd'))
|
||||||
|
VRFailed -> Left (corrId', entId', ERR AUTH)
|
||||||
|
Right _ -> pure $ Left (corrId', entId', ERR $ CMD PROHIBITED)
|
||||||
|
|
||||||
deliverMessage :: T.Text -> QueueRec -> RecipientId -> TVar Sub -> MsgQueue -> Maybe Message -> M (Transmission BrokerMsg)
|
deliverMessage :: T.Text -> QueueRec -> RecipientId -> TVar Sub -> MsgQueue -> Maybe Message -> M (Transmission BrokerMsg)
|
||||||
deliverMessage name qr rId sub q msg_ = time (name <> " deliver") $ do
|
deliverMessage name qr rId sub q msg_ = time (name <> " deliver") $ do
|
||||||
readTVarIO sub >>= \case
|
readTVarIO sub >>= \case
|
||||||
@@ -936,15 +1026,6 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Serv
|
|||||||
Right q -> updateDeletedStats q $> ok
|
Right q -> updateDeletedStats q $> ok
|
||||||
Left e -> pure $ err e
|
Left e -> pure $ err e
|
||||||
|
|
||||||
setupProxy :: SMPServer -> M (Transmission BrokerMsg)
|
|
||||||
setupProxy todo'relay = undefined
|
|
||||||
-- do
|
|
||||||
-- let relaySessionId = "TODO: relaySessionId"
|
|
||||||
-- (dummyRelayDhPublic, _) <- atomically . C.generateKeyPair =<< asks random
|
|
||||||
-- (_, dummySignKey) <- atomically . C.generateKeyPair =<< asks random
|
|
||||||
-- let dummyRelayKeySignature = C.sign' dummySignKey $ smpEncode dummyRelayDhPublic
|
|
||||||
-- pure (corrId, relaySessionId, PKEY dummyRelayDhPublic dummyRelayKeySignature)
|
|
||||||
|
|
||||||
ok :: Transmission BrokerMsg
|
ok :: Transmission BrokerMsg
|
||||||
ok = (corrId, queueId, OK)
|
ok = (corrId, queueId, OK)
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,14 @@ import qualified Data.IntMap.Strict as IM
|
|||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import Data.Text (Text)
|
|
||||||
import Data.Time.Clock (getCurrentTime)
|
import Data.Time.Clock (getCurrentTime)
|
||||||
import Data.Time.Clock.System (SystemTime)
|
import Data.Time.Clock.System (SystemTime)
|
||||||
import Data.X509.Validation (Fingerprint (..))
|
import Data.X509.Validation (Fingerprint (..))
|
||||||
import Network.Socket (ServiceName)
|
import Network.Socket (ServiceName)
|
||||||
import qualified Network.TLS as T
|
import qualified Network.TLS as T
|
||||||
import Numeric.Natural
|
import Numeric.Natural
|
||||||
import Simplex.Messaging.Agent.Env.SQLite (Worker)
|
|
||||||
import Simplex.Messaging.Agent.Lock
|
import Simplex.Messaging.Agent.Lock
|
||||||
|
import Simplex.Messaging.Client.Agent (SMPClientAgent, SMPClientAgentConfig, newSMPClientAgent)
|
||||||
import Simplex.Messaging.Crypto (KeyHash (..))
|
import Simplex.Messaging.Crypto (KeyHash (..))
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Protocol
|
import Simplex.Messaging.Protocol
|
||||||
@@ -35,7 +34,7 @@ import Simplex.Messaging.Server.Stats
|
|||||||
import Simplex.Messaging.Server.StoreLog
|
import Simplex.Messaging.Server.StoreLog
|
||||||
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, SessionId, VersionSMP, VersionRangeSMP)
|
import Simplex.Messaging.Transport (ATransport, VersionRangeSMP, VersionSMP)
|
||||||
import Simplex.Messaging.Transport.Server (SocketState, TransportServerConfig, loadFingerprint, loadTLSServerParams, newSocketState)
|
import Simplex.Messaging.Transport.Server (SocketState, TransportServerConfig, loadFingerprint, loadTLSServerParams, newSocketState)
|
||||||
import System.IO (IOMode (..))
|
import System.IO (IOMode (..))
|
||||||
import System.Mem.Weak (Weak)
|
import System.Mem.Weak (Weak)
|
||||||
@@ -82,6 +81,7 @@ data ServerConfig = ServerConfig
|
|||||||
transportConfig :: TransportServerConfig,
|
transportConfig :: TransportServerConfig,
|
||||||
-- | run listener on control port
|
-- | run listener on control port
|
||||||
controlPort :: Maybe ServiceName,
|
controlPort :: Maybe ServiceName,
|
||||||
|
smpAgentCfg :: SMPClientAgentConfig,
|
||||||
allowSMPProxy :: Bool -- auth is the same with `newQueueBasicAuth`
|
allowSMPProxy :: Bool -- auth is the same with `newQueueBasicAuth`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ data Env = Env
|
|||||||
sockets :: SocketState,
|
sockets :: SocketState,
|
||||||
clientSeq :: TVar ClientId,
|
clientSeq :: TVar ClientId,
|
||||||
clients :: TVar (IntMap Client),
|
clients :: TVar (IntMap Client),
|
||||||
proxyServer :: SMPProxyServer -- senders served on this proxy
|
proxyAgent :: ProxyAgent -- senders served on this proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
data Server = Server
|
data Server = Server
|
||||||
@@ -126,16 +126,8 @@ data Server = Server
|
|||||||
savingLock :: Lock
|
savingLock :: Lock
|
||||||
}
|
}
|
||||||
|
|
||||||
data SMPProxyServer = SMPProxyServer
|
data ProxyAgent = ProxyAgent
|
||||||
{ relaySessions :: TMap SessionId SMPProxiedRelay,
|
{ smpAgent :: SMPClientAgent
|
||||||
relayServers :: TMap Text SessionId -- speed up client lookups by server URI
|
|
||||||
}
|
|
||||||
|
|
||||||
data SMPProxiedRelay = SMPProxiedRelay
|
|
||||||
{ worker :: Worker,
|
|
||||||
proxyKey :: C.DhSecretX25519,
|
|
||||||
fwdQ :: TBQueue (ClientId, CorrId, C.PublicKeyX25519, ByteString) -- FWD args from multiple clients using this server
|
|
||||||
-- can be used for QUOTA retries until the session is gone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientId = Int
|
type ClientId = Int
|
||||||
@@ -194,7 +186,7 @@ newSubscription subThread = do
|
|||||||
return Sub {subThread, delivered}
|
return Sub {subThread, delivered}
|
||||||
|
|
||||||
newEnv :: ServerConfig -> IO Env
|
newEnv :: ServerConfig -> IO Env
|
||||||
newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile, storeLogFile} = do
|
newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile, storeLogFile, smpAgentCfg} = do
|
||||||
server <- atomically newServer
|
server <- atomically newServer
|
||||||
queueStore <- atomically newQueueStore
|
queueStore <- atomically newQueueStore
|
||||||
msgStore <- atomically newMsgStore
|
msgStore <- atomically newMsgStore
|
||||||
@@ -207,8 +199,8 @@ newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile,
|
|||||||
sockets <- atomically newSocketState
|
sockets <- atomically newSocketState
|
||||||
clientSeq <- newTVarIO 0
|
clientSeq <- newTVarIO 0
|
||||||
clients <- newTVarIO mempty
|
clients <- newTVarIO mempty
|
||||||
proxyServer <- newSMPProxyServer
|
proxyAgent <- atomically $ newSMPProxyAgent smpAgentCfg random
|
||||||
return Env {config, server, serverIdentity, queueStore, msgStore, random, storeLog, tlsServerParams, serverStats, sockets, clientSeq, clients, proxyServer}
|
return Env {config, server, serverIdentity, queueStore, msgStore, random, storeLog, tlsServerParams, serverStats, sockets, clientSeq, clients, proxyAgent}
|
||||||
where
|
where
|
||||||
restoreQueues :: QueueStore -> FilePath -> IO (StoreLog 'WriteMode)
|
restoreQueues :: QueueStore -> FilePath -> IO (StoreLog 'WriteMode)
|
||||||
restoreQueues QueueStore {queues, senders, notifiers} f = do
|
restoreQueues QueueStore {queues, senders, notifiers} f = do
|
||||||
@@ -225,8 +217,7 @@ newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile,
|
|||||||
Nothing -> id
|
Nothing -> id
|
||||||
Just NtfCreds {notifierId} -> M.insert notifierId (recipientId q)
|
Just NtfCreds {notifierId} -> M.insert notifierId (recipientId q)
|
||||||
|
|
||||||
newSMPProxyServer :: MonadIO m => m SMPProxyServer
|
newSMPProxyAgent :: SMPClientAgentConfig -> TVar ChaChaDRG -> STM ProxyAgent
|
||||||
newSMPProxyServer = do
|
newSMPProxyAgent smpAgentCfg random = do
|
||||||
relayServers <- atomically TM.empty
|
smpAgent <- newSMPClientAgent smpAgentCfg random
|
||||||
relaySessions <- atomically TM.empty
|
pure ProxyAgent {smpAgent}
|
||||||
pure SMPProxyServer {relayServers, relaySessions}
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import qualified Data.Text as T
|
|||||||
import Data.Text.Encoding (encodeUtf8)
|
import Data.Text.Encoding (encodeUtf8)
|
||||||
import Network.Socket (HostName)
|
import Network.Socket (HostName)
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
|
import Simplex.Messaging.Client (ProtocolClientConfig (..))
|
||||||
|
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
||||||
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 (BasicAuth (..), ProtoServerWithAuth (ProtoServerWithAuth), pattern SMPServer)
|
import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (ProtoServerWithAuth), pattern SMPServer)
|
||||||
@@ -25,10 +27,11 @@ import Simplex.Messaging.Server (runSMPServer)
|
|||||||
import Simplex.Messaging.Server.CLI
|
import Simplex.Messaging.Server.CLI
|
||||||
import Simplex.Messaging.Server.Env.STM (ServerConfig (..), defMsgExpirationDays, defaultInactiveClientExpiration, defaultMessageExpiration)
|
import Simplex.Messaging.Server.Env.STM (ServerConfig (..), defMsgExpirationDays, defaultInactiveClientExpiration, defaultMessageExpiration)
|
||||||
import Simplex.Messaging.Server.Expiration
|
import Simplex.Messaging.Server.Expiration
|
||||||
import Simplex.Messaging.Transport (simplexMQVersion, supportedServerSMPRelayVRange)
|
import Simplex.Messaging.Transport (simplexMQVersion, supportedServerSMPRelayVRange, batchCmdsSMPVersion, sendingProxySMPVersion)
|
||||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||||
import Simplex.Messaging.Transport.Server (TransportServerConfig (..), defaultTransportServerConfig)
|
import Simplex.Messaging.Transport.Server (TransportServerConfig (..), defaultTransportServerConfig)
|
||||||
import Simplex.Messaging.Util (safeDecodeUtf8)
|
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||||
|
import Simplex.Messaging.Version (mkVersionRange)
|
||||||
import System.Directory (createDirectoryIfMissing, doesFileExist)
|
import System.Directory (createDirectoryIfMissing, doesFileExist)
|
||||||
import System.FilePath (combine)
|
import System.FilePath (combine)
|
||||||
import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
|
import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
|
||||||
@@ -214,6 +217,7 @@ smpServerCLI cfgPath logPath =
|
|||||||
{ logTLSErrors = fromMaybe False $ iniOnOff "TRANSPORT" "log_tls_errors" ini
|
{ logTLSErrors = fromMaybe False $ iniOnOff "TRANSPORT" "log_tls_errors" ini
|
||||||
},
|
},
|
||||||
controlPort = either (const Nothing) (Just . T.unpack) $ lookupValue "TRANSPORT" "control_port" ini,
|
controlPort = either (const Nothing) (Just . T.unpack) $ lookupValue "TRANSPORT" "control_port" ini,
|
||||||
|
smpAgentCfg = defaultSMPClientAgentConfig {smpCfg = (smpCfg defaultSMPClientAgentConfig) {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion}},
|
||||||
allowSMPProxy = True -- TODO: "get from INI"
|
allowSMPProxy = True -- TODO: "get from INI"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,4 +310,3 @@ cliCommandP cfgPath logPath iniFile =
|
|||||||
pure InitOptions {enableStoreLog, logStats, signAlgorithm, ip, fqdn, password, scripted}
|
pure InitOptions {enableStoreLog, logStats, signAlgorithm, ip, fqdn, password, scripted}
|
||||||
parseBasicAuth :: ReadM ServerPassword
|
parseBasicAuth :: ReadM ServerPassword
|
||||||
parseBasicAuth = eitherReader $ fmap ServerPassword . strDecode . B.pack
|
parseBasicAuth = eitherReader $ fmap ServerPassword . strDecode . B.pack
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ addQueue QueueStore {queues, senders} q@QueueRec {recipientId = rId, senderId =
|
|||||||
where
|
where
|
||||||
hasId = (||) <$> TM.member rId queues <*> TM.member sId senders
|
hasId = (||) <$> TM.member rId queues <*> TM.member sId senders
|
||||||
|
|
||||||
getQueue :: QueueStore -> SParty p -> QueueId -> STM (Either ErrorType QueueRec)
|
getQueue :: DirectParty p => QueueStore -> SParty p -> QueueId -> STM (Either ErrorType QueueRec)
|
||||||
getQueue QueueStore {queues, senders, notifiers} party qId =
|
getQueue QueueStore {queues, senders, notifiers} party qId =
|
||||||
toResult <$> (mapM readTVar =<< getVar)
|
toResult <$> (mapM readTVar =<< getVar)
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ import UnliftIO.STM
|
|||||||
|
|
||||||
-- * Transport parameters
|
-- * Transport parameters
|
||||||
|
|
||||||
|
-- min size it works with:
|
||||||
|
-- unsigned message: 16292 (paddedProxiedMsgLength = 16151, paddedForwardedMsgLength = 16239)
|
||||||
|
-- Ed448: 16406 (16384 + 22, fails with 21)
|
||||||
|
-- Ed25519: 16356
|
||||||
|
-- X25519: 16381
|
||||||
smpBlockSize :: Int
|
smpBlockSize :: Int
|
||||||
smpBlockSize = 16384
|
smpBlockSize = 16384
|
||||||
|
|
||||||
@@ -358,6 +363,7 @@ data ServerHandshake = ServerHandshake
|
|||||||
{ smpVersionRange :: VersionRangeSMP,
|
{ smpVersionRange :: VersionRangeSMP,
|
||||||
sessionId :: SessionId,
|
sessionId :: SessionId,
|
||||||
-- pub key to agree shared secrets for command authorization and entity ID encryption.
|
-- pub key to agree shared secrets for command authorization and entity ID encryption.
|
||||||
|
-- todo C.PublicKeyX25519
|
||||||
authPubKey :: Maybe (X.CertificateChain, X.SignedExact X.PubKey)
|
authPubKey :: Maybe (X.CertificateChain, X.SignedExact X.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,12 +281,12 @@ randomSUB_ :: (C.AlgorithmI a, C.AuthAlgorithm a) => C.SAlgorithm a -> VersionSM
|
|||||||
randomSUB_ a v sessId = do
|
randomSUB_ a v sessId = do
|
||||||
g <- C.newRandom
|
g <- C.newRandom
|
||||||
rId <- atomically $ C.randomBytes 24 g
|
rId <- atomically $ C.randomBytes 24 g
|
||||||
corrId <- atomically $ CorrId <$> C.randomBytes 24 g
|
nonce@(C.CbNonce corrId) <- atomically $ C.randomCbNonce g
|
||||||
(rKey, rpKey) <- atomically $ C.generateAuthKeyPair a g
|
(rKey, rpKey) <- atomically $ C.generateAuthKeyPair a g
|
||||||
thAuth_ <- testTHandleAuth v g rKey
|
thAuth_ <- testTHandleAuth v g rKey
|
||||||
let thParams = testTHandleParams v sessId
|
let thParams = testTHandleParams v sessId
|
||||||
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, rId, Cmd SRecipient SUB)
|
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (CorrId corrId, rId, Cmd SRecipient SUB)
|
||||||
pure $ (,tToSend) <$> authTransmission thAuth_ (Just rpKey) corrId tForAuth
|
pure $ (,tToSend) <$> authTransmission thAuth_ (Just rpKey) nonce tForAuth
|
||||||
|
|
||||||
randomSUBCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> IO (PCTransmission ErrorType BrokerMsg)
|
randomSUBCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> IO (PCTransmission ErrorType BrokerMsg)
|
||||||
randomSUBCmd = randomSUBCmd_ C.SEd25519
|
randomSUBCmd = randomSUBCmd_ C.SEd25519
|
||||||
@@ -311,13 +311,13 @@ randomSEND_ :: (C.AlgorithmI a, C.AuthAlgorithm a) => C.SAlgorithm a -> VersionS
|
|||||||
randomSEND_ a v sessId len = do
|
randomSEND_ a v sessId len = do
|
||||||
g <- C.newRandom
|
g <- C.newRandom
|
||||||
sId <- atomically $ C.randomBytes 24 g
|
sId <- atomically $ C.randomBytes 24 g
|
||||||
corrId <- atomically $ CorrId <$> C.randomBytes 3 g
|
nonce@(C.CbNonce corrId) <- atomically $ C.randomCbNonce g
|
||||||
(sKey, spKey) <- atomically $ C.generateAuthKeyPair a g
|
(sKey, spKey) <- atomically $ C.generateAuthKeyPair a g
|
||||||
thAuth_ <- testTHandleAuth v g sKey
|
thAuth_ <- testTHandleAuth v g sKey
|
||||||
msg <- atomically $ C.randomBytes len g
|
msg <- atomically $ C.randomBytes len g
|
||||||
let thParams = testTHandleParams v sessId
|
let thParams = testTHandleParams v sessId
|
||||||
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, sId, Cmd SSender $ SEND noMsgFlags msg)
|
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (CorrId corrId, sId, Cmd SSender $ SEND noMsgFlags msg)
|
||||||
pure $ (,tToSend) <$> authTransmission thAuth_ (Just spKey) corrId tForAuth
|
pure $ (,tToSend) <$> authTransmission thAuth_ (Just spKey) nonce tForAuth
|
||||||
|
|
||||||
testTHandleParams :: VersionSMP -> ByteString -> THandleParams SMPVersion 'TClient
|
testTHandleParams :: VersionSMP -> ByteString -> THandleParams SMPVersion 'TClient
|
||||||
testTHandleParams v sessionId =
|
testTHandleParams v sessionId =
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE StandaloneDeriving #-}
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||||
|
|
||||||
module CoreTests.ProtocolErrorTests where
|
module CoreTests.ProtocolErrorTests where
|
||||||
@@ -14,9 +13,10 @@ import GHC.Generics (Generic)
|
|||||||
import Generic.Random (genericArbitraryU)
|
import Generic.Random (genericArbitraryU)
|
||||||
import Simplex.FileTransfer.Transport (XFTPErrorType (..))
|
import Simplex.FileTransfer.Transport (XFTPErrorType (..))
|
||||||
import Simplex.Messaging.Agent.Protocol
|
import Simplex.Messaging.Agent.Protocol
|
||||||
|
import qualified Simplex.Messaging.Agent.Protocol as Agent
|
||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Protocol (CommandError (..), ErrorType (..))
|
import Simplex.Messaging.Protocol (CommandError (..), ErrorType (..), ProxyError (..))
|
||||||
import Simplex.Messaging.Transport (HandshakeError (..), TransportError (..))
|
import Simplex.Messaging.Transport (HandshakeError (..), TransportError (..))
|
||||||
import Simplex.RemoteControl.Types (RCErrorType (..))
|
import Simplex.RemoteControl.Types (RCErrorType (..))
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
@@ -33,7 +33,7 @@ protocolErrorTests = modifyMaxSuccess (const 1000) $ do
|
|||||||
|| strDecode (strEncode err) == Right err
|
|| strDecode (strEncode err) == Right err
|
||||||
where
|
where
|
||||||
errHasSpaces = \case
|
errHasSpaces = \case
|
||||||
BROKER srv (RESPONSE e) -> hasSpaces srv || hasSpaces e
|
BROKER srv (Agent.RESPONSE e) -> hasSpaces srv || hasSpaces e
|
||||||
BROKER srv _ -> hasSpaces srv
|
BROKER srv _ -> hasSpaces srv
|
||||||
_ -> False
|
_ -> False
|
||||||
hasSpaces s = ' ' `B.elem` encodeUtf8 (T.pack s)
|
hasSpaces s = ' ' `B.elem` encodeUtf8 (T.pack s)
|
||||||
@@ -54,6 +54,8 @@ deriving instance Generic ErrorType
|
|||||||
|
|
||||||
deriving instance Generic CommandError
|
deriving instance Generic CommandError
|
||||||
|
|
||||||
|
deriving instance Generic ProxyError
|
||||||
|
|
||||||
deriving instance Generic TransportError
|
deriving instance Generic TransportError
|
||||||
|
|
||||||
deriving instance Generic HandshakeError
|
deriving instance Generic HandshakeError
|
||||||
@@ -78,6 +80,8 @@ instance Arbitrary ErrorType where arbitrary = genericArbitraryU
|
|||||||
|
|
||||||
instance Arbitrary CommandError where arbitrary = genericArbitraryU
|
instance Arbitrary CommandError where arbitrary = genericArbitraryU
|
||||||
|
|
||||||
|
instance Arbitrary ProxyError where arbitrary = genericArbitraryU
|
||||||
|
|
||||||
instance Arbitrary TransportError where arbitrary = genericArbitraryU
|
instance Arbitrary TransportError where arbitrary = genericArbitraryU
|
||||||
|
|
||||||
instance Arbitrary HandshakeError where arbitrary = genericArbitraryU
|
instance Arbitrary HandshakeError where arbitrary = genericArbitraryU
|
||||||
|
|||||||
+9
-2
@@ -16,7 +16,8 @@ import Control.Monad.Except (runExceptT)
|
|||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Network.Socket
|
import Network.Socket
|
||||||
import Simplex.Messaging.Client (chooseTransportHost, defaultNetworkConfig)
|
import Simplex.Messaging.Client (ProtocolClientConfig (..), chooseTransportHost, defaultNetworkConfig)
|
||||||
|
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
||||||
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.Protocol
|
import Simplex.Messaging.Protocol
|
||||||
@@ -112,6 +113,7 @@ cfg =
|
|||||||
smpServerVRange = supportedServerSMPRelayVRange,
|
smpServerVRange = supportedServerSMPRelayVRange,
|
||||||
transportConfig = defaultTransportServerConfig,
|
transportConfig = defaultTransportServerConfig,
|
||||||
controlPort = Nothing,
|
controlPort = Nothing,
|
||||||
|
smpAgentCfg = defaultSMPClientAgentConfig,
|
||||||
allowSMPProxy = False
|
allowSMPProxy = False
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +121,12 @@ cfgV7 :: ServerConfig
|
|||||||
cfgV7 = cfg {smpServerVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion}
|
cfgV7 = cfg {smpServerVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion}
|
||||||
|
|
||||||
proxyCfg :: ServerConfig
|
proxyCfg :: ServerConfig
|
||||||
proxyCfg = cfg {allowSMPProxy = True}
|
proxyCfg =
|
||||||
|
cfgV7
|
||||||
|
{ allowSMPProxy = True,
|
||||||
|
smpServerVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion,
|
||||||
|
smpAgentCfg = defaultSMPClientAgentConfig {smpCfg = (smpCfg defaultSMPClientAgentConfig) {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion}}
|
||||||
|
}
|
||||||
|
|
||||||
withSmpServerStoreMsgLogOn :: HasCallStack => ATransport -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a
|
withSmpServerStoreMsgLogOn :: HasCallStack => ATransport -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a
|
||||||
withSmpServerStoreMsgLogOn t = withSmpServerConfigOn t cfg {storeLogFile = Just testStoreLogFile, storeMsgsFile = Just testStoreMsgsFile, serverStatsBackupFile = Just testServerStatsBackupFile}
|
withSmpServerStoreMsgLogOn t = withSmpServerConfigOn t cfg {storeLogFile = Just testStoreLogFile, storeMsgsFile = Just testStoreMsgsFile, serverStatsBackupFile = Just testServerStatsBackupFile}
|
||||||
|
|||||||
+93
-24
@@ -1,38 +1,117 @@
|
|||||||
{-# LANGUAGE DataKinds #-}
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
module SMPProxyTests where
|
module SMPProxyTests where
|
||||||
|
|
||||||
import Debug.Trace
|
import AgentTests.FunctionalAPITests (runRight_)
|
||||||
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import SMPAgentClient (testSMPServer, testSMPServer2)
|
import SMPAgentClient (testSMPServer, testSMPServer2)
|
||||||
import SMPClient
|
import SMPClient
|
||||||
import ServerTests (sendRecv)
|
import qualified SMPClient as SMP
|
||||||
|
import ServerTests (decryptMsgV3, sendRecv)
|
||||||
|
import Simplex.Messaging.Client
|
||||||
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Protocol
|
import Simplex.Messaging.Protocol
|
||||||
import Simplex.Messaging.Server.Env.STM (ServerConfig (..))
|
import Simplex.Messaging.Server.Env.STM (ServerConfig (..))
|
||||||
import Simplex.Messaging.Transport
|
import Simplex.Messaging.Transport
|
||||||
import Simplex.Messaging.Version (mkVersionRange)
|
import Simplex.Messaging.Version (mkVersionRange)
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
import UnliftIO
|
||||||
|
|
||||||
smpProxyTests :: Spec
|
smpProxyTests :: Spec
|
||||||
smpProxyTests = do
|
smpProxyTests = do
|
||||||
describe "server configuration" $ do
|
describe "server configuration" $ do
|
||||||
it "refuses proxy handshake unless enabled" testNoProxy
|
it "refuses proxy handshake unless enabled" testNoProxy
|
||||||
it "checks basic auth in proxy requests" testProxyAuth
|
it "checks basic auth in proxy requests" testProxyAuth
|
||||||
xdescribe "proxy requests" $ do
|
describe "proxy requests" $ do
|
||||||
xdescribe "bad relay URIs" $ do
|
describe "bad relay URIs" $ do
|
||||||
it "host not resolved" todo
|
xit "host not resolved" todo
|
||||||
it "when SMP port blackholed" todo
|
xit "when SMP port blackholed" todo
|
||||||
it "no SMP service at host/port" todo
|
xit "no SMP service at host/port" todo
|
||||||
it "bad SMP fingerprint" todo
|
xit "bad SMP fingerprint" todo
|
||||||
it "connects to relay" testProxyConnect
|
|
||||||
xit "connects to itself as a relay" todo
|
|
||||||
xit "batching proxy requests" todo
|
xit "batching proxy requests" todo
|
||||||
xdescribe "forwarding requests" $ do
|
describe "forwarding requests" $ do
|
||||||
it "sender-proxy-relay-recipient works" todo
|
describe "deliver message via SMP proxy" $ do
|
||||||
it "similar timing for proxied and direct sends" todo
|
it "same server" $
|
||||||
|
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ -> do
|
||||||
|
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
|
||||||
|
let relayServ = proxyServ
|
||||||
|
deliverMessageViaProxy proxyServ relayServ C.SEd448 "hello 1" "hello 2"
|
||||||
|
it "different servers" $
|
||||||
|
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
|
||||||
|
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
|
||||||
|
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
|
||||||
|
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
|
||||||
|
deliverMessageViaProxy proxyServ relayServ C.SEd448 "hello 1" "hello 2"
|
||||||
|
xit "max message size, Ed448 keys" $
|
||||||
|
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
|
||||||
|
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
|
||||||
|
g <- C.newRandom
|
||||||
|
msg <- atomically $ C.randomBytes maxMessageLength g
|
||||||
|
msg' <- atomically $ C.randomBytes maxMessageLength g
|
||||||
|
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
|
||||||
|
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
|
||||||
|
deliverMessageViaProxy proxyServ relayServ C.SEd448 msg msg'
|
||||||
|
it "max message size, Ed25519 keys" $
|
||||||
|
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
|
||||||
|
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
|
||||||
|
g <- C.newRandom
|
||||||
|
msg <- atomically $ C.randomBytes maxMessageLength g
|
||||||
|
msg' <- atomically $ C.randomBytes maxMessageLength g
|
||||||
|
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
|
||||||
|
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
|
||||||
|
deliverMessageViaProxy proxyServ relayServ C.SEd25519 msg msg'
|
||||||
|
it "max message size, X25519 keys" $
|
||||||
|
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
|
||||||
|
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
|
||||||
|
g <- C.newRandom
|
||||||
|
msg <- atomically $ C.randomBytes maxMessageLength g
|
||||||
|
msg' <- atomically $ C.randomBytes maxMessageLength g
|
||||||
|
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
|
||||||
|
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
|
||||||
|
deliverMessageViaProxy proxyServ relayServ C.SX25519 msg msg'
|
||||||
|
xit "sender-proxy-relay-recipient works" todo
|
||||||
|
xit "similar timing for proxied and direct sends" todo
|
||||||
|
|
||||||
|
deliverMessageViaProxy :: (C.AlgorithmI a, C.AuthAlgorithm a) => SMPServer -> SMPServer -> C.SAlgorithm a -> ByteString -> ByteString -> IO ()
|
||||||
|
deliverMessageViaProxy proxyServ relayServ alg msg msg' = do
|
||||||
|
g <- C.newRandom
|
||||||
|
-- set up proxy
|
||||||
|
Right pc <- getProtocolClient g (1, proxyServ, Nothing) defaultSMPClientConfig {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion} Nothing (\_ -> pure ())
|
||||||
|
THAuthClient {} <- maybe (fail "getProtocolClient returned no thAuth") pure $ thAuth $ thParams pc
|
||||||
|
-- set up relay
|
||||||
|
msgQ <- newTBQueueIO 4
|
||||||
|
Right rc <- getProtocolClient g (2, relayServ, Nothing) defaultSMPClientConfig {serverVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion} (Just msgQ) (\_ -> pure ())
|
||||||
|
runRight_ $ do
|
||||||
|
-- prepare receiving queue
|
||||||
|
(rPub, rPriv) <- atomically $ C.generateAuthKeyPair alg g
|
||||||
|
(rdhPub, rdhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
||||||
|
QIK {rcvId, sndId, rcvPublicDhKey = srvDh} <- createSMPQueue rc (rPub, rPriv) rdhPub (Just "correct") SMSubscribe
|
||||||
|
let dec = decryptMsgV3 $ C.dh' srvDh rdhPriv
|
||||||
|
-- get proxy session
|
||||||
|
(sessId, v, relayKey) <- createSMPProxySession pc relayServ (Just "correct")
|
||||||
|
-- send via proxy to unsecured queue
|
||||||
|
proxySMPMessage pc sessId v relayKey Nothing sndId noMsgFlags msg
|
||||||
|
-- receive 1
|
||||||
|
(_tSess, _v, _sid, _ety, MSG RcvMessage {msgId, msgBody = EncRcvMsgBody encBody}) <- atomically $ readTBQueue msgQ
|
||||||
|
liftIO $ dec msgId encBody `shouldBe` Right msg
|
||||||
|
ackSMPMessage rc rPriv rcvId msgId
|
||||||
|
-- secure queue
|
||||||
|
(sPub, sPriv) <- atomically $ C.generateAuthKeyPair alg g
|
||||||
|
secureSMPQueue rc rPriv rcvId sPub
|
||||||
|
-- send via proxy to secured queue
|
||||||
|
proxySMPMessage pc sessId v relayKey (Just sPriv) sndId noMsgFlags msg'
|
||||||
|
-- receive 2
|
||||||
|
(_tSess, _v, _sid, _ety, MSG RcvMessage {msgId = msgId', msgBody = EncRcvMsgBody encBody'}) <- atomically $ readTBQueue msgQ
|
||||||
|
liftIO $ dec msgId' encBody' `shouldBe` Right msg'
|
||||||
|
ackSMPMessage rc rPriv rcvId msgId'
|
||||||
|
|
||||||
proxyVRange :: VersionRangeSMP
|
proxyVRange :: VersionRangeSMP
|
||||||
proxyVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion
|
proxyVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion
|
||||||
@@ -48,21 +127,11 @@ testProxyAuth :: IO ()
|
|||||||
testProxyAuth = do
|
testProxyAuth = do
|
||||||
withSmpServerConfigOn (transport @TLS) proxyCfgAuth testPort $ \_ -> do
|
withSmpServerConfigOn (transport @TLS) proxyCfgAuth testPort $ \_ -> do
|
||||||
testSMPClient_ "127.0.0.1" testPort proxyVRange $ \(th :: THandleSMP TLS 'TClient) -> do
|
testSMPClient_ "127.0.0.1" testPort proxyVRange $ \(th :: THandleSMP TLS 'TClient) -> do
|
||||||
(_, s, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer2 $ Just "wrong")
|
(_, _s, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer2 $ Just "wrong")
|
||||||
traceShowM s
|
|
||||||
reply `shouldBe` Right (ERR AUTH)
|
reply `shouldBe` Right (ERR AUTH)
|
||||||
where
|
where
|
||||||
proxyCfgAuth = proxyCfg {newQueueBasicAuth = Just "correct"}
|
proxyCfgAuth = proxyCfg {newQueueBasicAuth = Just "correct"}
|
||||||
|
|
||||||
testProxyConnect :: IO ()
|
|
||||||
testProxyConnect = do
|
|
||||||
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ -> do
|
|
||||||
testSMPClient_ "127.0.0.1" testPort proxyVRange $ \(th :: THandleSMP TLS 'TClient) -> do
|
|
||||||
(_, _, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer2 Nothing)
|
|
||||||
case reply of
|
|
||||||
Right PKEY {} -> pure ()
|
|
||||||
_ -> fail $ "bad reply: " <> show reply
|
|
||||||
|
|
||||||
todo :: IO ()
|
todo :: IO ()
|
||||||
todo = do
|
todo = do
|
||||||
fail "TODO"
|
fail "TODO"
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ main = do
|
|||||||
$ do
|
$ do
|
||||||
describe "Agent SQLite schema dump" schemaDumpTest
|
describe "Agent SQLite schema dump" schemaDumpTest
|
||||||
describe "Core tests" $ do
|
describe "Core tests" $ do
|
||||||
describe "Batching tests" batchingTests
|
xdescribe "Batching tests" batchingTests
|
||||||
describe "Encoding tests" encodingTests
|
describe "Encoding tests" encodingTests
|
||||||
describe "Protocol error tests" protocolErrorTests
|
describe "Protocol error tests" protocolErrorTests
|
||||||
describe "Version range" versionRangeTests
|
describe "Version range" versionRangeTests
|
||||||
|
|||||||
Reference in New Issue
Block a user