mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-03-30 14:16:00 +00:00
proxy: reporting errors (#1108)
* smp-proxy: iron out errors * treat proxy timeouts as temporary * update errors * proxy errors (missing encoding) * update * enable tests * update * update * fix * fix * simplify * test --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
committed by
GitHub
parent
f22daf5cb6
commit
c85f6a2f0e
@@ -2250,9 +2250,9 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
| otherwise -> ignored
|
||||
_ -> ignored
|
||||
ignored = pure "END from disconnected client - ignored"
|
||||
_ -> do
|
||||
r -> do
|
||||
logServer "<--" c srv rId $ "unexpected: " <> bshow cmd
|
||||
notify . ERR $ BROKER (B.unpack $ strEncode srv) $ if isResponse then TIMEOUT else UNEXPECTED
|
||||
notify . ERR $ BROKER (B.unpack $ strEncode srv) $ if isResponse then TIMEOUT else UNEXPECTED $ take 32 $ show r
|
||||
where
|
||||
notify :: forall e m. MonadIO m => AEntityI e => ACommand 'Agent e -> m ()
|
||||
notify = atomically . notify'
|
||||
|
||||
@@ -1032,8 +1032,15 @@ sendOrProxySMPMessage c userId destSrv cmdStr spKey_ senderId msgFlags msg = do
|
||||
unknownServer = maybe True (all ((destSrv /=) . protoServer)) <$> TM.lookup userId (userServers c)
|
||||
sendViaProxy destSess =
|
||||
withProxySession c destSess senderId ("PFWD " <> cmdStr) $ \(SMPConnectedClient smp _, proxySess) -> do
|
||||
liftClient SMP (clientServer smp) $ proxySMPMessage smp proxySess spKey_ senderId msgFlags msg
|
||||
pure . Just $ protocolClientServer' smp
|
||||
liftClient SMP (clientServer smp) (proxySMPMessage smp proxySess spKey_ senderId msgFlags msg) >>= \case
|
||||
Right () -> pure . Just $ protocolClientServer' smp
|
||||
Left proxyErr ->
|
||||
throwError
|
||||
PROXY
|
||||
{ proxyServer = protocolClientServer smp,
|
||||
relayServer = B.unpack $ strEncode destSrv,
|
||||
proxyErr
|
||||
}
|
||||
sendDirectly tSess =
|
||||
withLogClient_ c tSess senderId ("SEND " <> cmdStr) $ \(SMPConnectedClient smp _) ->
|
||||
liftClient SMP (clientServer smp) $ sendSMPMessage smp spKey_ senderId msgFlags msg
|
||||
@@ -1066,7 +1073,7 @@ protocolClientError :: (Show err, Encoding err) => (err -> AgentErrorType) -> Ho
|
||||
protocolClientError protocolError_ host = \case
|
||||
PCEProtocolError e -> protocolError_ e
|
||||
PCEResponseError e -> BROKER host $ RESPONSE $ B.unpack $ smpEncode e
|
||||
PCEUnexpectedResponse _ -> BROKER host UNEXPECTED
|
||||
PCEUnexpectedResponse r -> BROKER host $ UNEXPECTED $ take 32 $ show r
|
||||
PCEResponseTimeout -> BROKER host TIMEOUT
|
||||
PCENetworkError -> BROKER host NETWORK
|
||||
PCEIncompatibleHost -> BROKER host HOST
|
||||
@@ -1263,15 +1270,23 @@ processSubResult c rq r = do
|
||||
|
||||
temporaryAgentError :: AgentErrorType -> Bool
|
||||
temporaryAgentError = \case
|
||||
BROKER _ NETWORK -> True
|
||||
BROKER _ TIMEOUT -> True
|
||||
BROKER _ e -> tempBrokerError e
|
||||
SMP (SMP.PROXY (SMP.BROKER e)) -> tempBrokerError e
|
||||
PROXY _ _ (ProxyProtocolError (SMP.PROXY (SMP.BROKER e))) -> tempBrokerError e
|
||||
INACTIVE -> True
|
||||
_ -> False
|
||||
where
|
||||
tempBrokerError = \case
|
||||
NETWORK -> True
|
||||
TIMEOUT -> True
|
||||
_ -> False
|
||||
{-# INLINE temporaryAgentError #-}
|
||||
|
||||
temporaryOrHostError :: AgentErrorType -> Bool
|
||||
temporaryOrHostError = \case
|
||||
BROKER _ HOST -> True
|
||||
SMP (SMP.PROXY (SMP.BROKER HOST)) -> True
|
||||
PROXY _ _ (ProxyProtocolError (SMP.PROXY (SMP.BROKER HOST))) -> True
|
||||
e -> temporaryAgentError e
|
||||
{-# INLINE temporaryOrHostError #-}
|
||||
|
||||
|
||||
@@ -189,6 +189,7 @@ import Simplex.FileTransfer.Description
|
||||
import Simplex.FileTransfer.Protocol (FileParty (..))
|
||||
import Simplex.FileTransfer.Transport (XFTPErrorType)
|
||||
import Simplex.Messaging.Agent.QueryString
|
||||
import Simplex.Messaging.Client (ProxyClientError)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet
|
||||
( InitialKeys (..),
|
||||
@@ -206,6 +207,7 @@ import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers
|
||||
import Simplex.Messaging.Protocol
|
||||
( AProtocolType,
|
||||
BrokerErrorType (..),
|
||||
EntityId,
|
||||
ErrorType,
|
||||
MsgBody,
|
||||
@@ -233,7 +235,7 @@ import Simplex.Messaging.Protocol
|
||||
)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.ServiceScheme
|
||||
import Simplex.Messaging.Transport (Transport (..), TransportError, serializeTransportError, transportErrorP)
|
||||
import Simplex.Messaging.Transport (Transport (..), serializeTransportError, transportErrorP)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost, TransportHosts_ (..))
|
||||
import Simplex.Messaging.Util
|
||||
import Simplex.Messaging.Version
|
||||
@@ -1474,6 +1476,8 @@ data AgentErrorType
|
||||
NTF {ntfErr :: ErrorType}
|
||||
| -- | XFTP protocol errors forwarded to agent clients
|
||||
XFTP {xftpErr :: XFTPErrorType}
|
||||
| -- | SMP proxy errors
|
||||
PROXY {proxyServer :: String, relayServer :: String, proxyErr :: ProxyClientError}
|
||||
| -- | XRCP protocol errors forwarded to agent clients
|
||||
RCP {rcpErr :: RCErrorType}
|
||||
| -- | SMP server errors
|
||||
@@ -1516,22 +1520,6 @@ data ConnectionErrorType
|
||||
NOT_AVAILABLE
|
||||
deriving (Eq, Read, Show, Exception)
|
||||
|
||||
-- | SMP server errors.
|
||||
data BrokerErrorType
|
||||
= -- | invalid server response (failed to parse)
|
||||
RESPONSE {smpErr :: String}
|
||||
| -- | unexpected response
|
||||
UNEXPECTED
|
||||
| -- | network error
|
||||
NETWORK
|
||||
| -- | no compatible server host (e.g. onion when public is required, or vice versa)
|
||||
HOST
|
||||
| -- | handshake or other transport error
|
||||
TRANSPORT {transportErr :: TransportError}
|
||||
| -- | command response timeout
|
||||
TIMEOUT
|
||||
deriving (Eq, Read, Show, Exception)
|
||||
|
||||
-- | Errors of another SMP agent.
|
||||
data SMPAgentError
|
||||
= -- | client or agent message that failed to parse
|
||||
@@ -1587,8 +1575,10 @@ instance StrEncoding AgentErrorType where
|
||||
<|> "SMP " *> (SMP <$> strP)
|
||||
<|> "NTF " *> (NTF <$> strP)
|
||||
<|> "XFTP " *> (XFTP <$> strP)
|
||||
<|> "PROXY " *> (PROXY <$> textP <* A.space <*> textP <*> _strP)
|
||||
<|> "RCP " *> (RCP <$> strP)
|
||||
<|> "BROKER " *> (BROKER <$> textP <* " RESPONSE " <*> (RESPONSE <$> textP))
|
||||
<|> "BROKER " *> (BROKER <$> textP <* " UNEXPECTED " <*> (UNEXPECTED <$> textP))
|
||||
<|> "BROKER " *> (BROKER <$> textP <* " TRANSPORT " <*> (TRANSPORT <$> transportErrorP))
|
||||
<|> "BROKER " *> (BROKER <$> textP <* A.space <*> parseRead1)
|
||||
<|> "AGENT CRYPTO " *> (AGENT . A_CRYPTO <$> parseRead A.takeByteString)
|
||||
@@ -1605,8 +1595,10 @@ instance StrEncoding AgentErrorType where
|
||||
SMP e -> "SMP " <> strEncode e
|
||||
NTF e -> "NTF " <> strEncode e
|
||||
XFTP e -> "XFTP " <> strEncode e
|
||||
PROXY pxy srv e -> B.unwords ["PROXY", text pxy, text srv, strEncode e]
|
||||
RCP e -> "RCP " <> strEncode e
|
||||
BROKER srv (RESPONSE e) -> "BROKER " <> text srv <> " RESPONSE " <> text e
|
||||
BROKER srv (UNEXPECTED e) -> "BROKER " <> text srv <> " UNEXPECTED " <> text e
|
||||
BROKER srv (TRANSPORT e) -> "BROKER " <> text srv <> " TRANSPORT " <> serializeTransportError e
|
||||
BROKER srv e -> "BROKER " <> text srv <> " " <> bshow e
|
||||
AGENT (A_CRYPTO e) -> "AGENT CRYPTO " <> bshow e
|
||||
@@ -1977,8 +1969,6 @@ $(J.deriveJSON (sumTypeJSON id) ''CommandErrorType)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON id) ''ConnectionErrorType)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON id) ''BrokerErrorType)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON id) ''AgentCryptoError)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON id) ''SMPAgentError)
|
||||
|
||||
@@ -64,6 +64,7 @@ module Simplex.Messaging.Client
|
||||
-- * Supporting types and client configuration
|
||||
ProtocolClientError (..),
|
||||
SMPClientError,
|
||||
ProxyClientError (..),
|
||||
ProtocolClientConfig (..),
|
||||
NetworkConfig (..),
|
||||
TransportSessionMode (..),
|
||||
@@ -97,6 +98,7 @@ import Control.Monad.IO.Class (liftIO)
|
||||
import Control.Monad.Trans.Except
|
||||
import Crypto.Random (ChaChaDRG)
|
||||
import qualified Data.Aeson.TH as J
|
||||
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Functor (($>))
|
||||
@@ -534,16 +536,17 @@ temporaryClientError = \case
|
||||
_ -> False
|
||||
{-# INLINE temporaryClientError #-}
|
||||
|
||||
-- converts error of client running on proxy to the error sent to client connected to proxy
|
||||
smpProxyError :: SMPClientError -> ErrorType
|
||||
smpProxyError = \case
|
||||
PCEProtocolError et -> PROXY (PROTOCOL et)
|
||||
PCEResponseError et -> PROXY (RESPONSE et)
|
||||
PCEUnexpectedResponse bs -> PROXY (UNEXPECTED $ B.unpack $ B.take 32 bs)
|
||||
PCEResponseTimeout -> PROXY TIMEOUT
|
||||
PCENetworkError -> PROXY NETWORK
|
||||
PCEIncompatibleHost -> PROXY BAD_HOST
|
||||
PCETransportError t -> PROXY (TRANSPORT t)
|
||||
PCECryptoError _ -> INTERNAL
|
||||
PCEProtocolError e -> PROXY $ PROTOCOL e
|
||||
PCEResponseError e -> PROXY $ BROKER $ RESPONSE $ B.unpack $ strEncode e
|
||||
PCEUnexpectedResponse s -> PROXY $ BROKER $ UNEXPECTED $ B.unpack $ B.take 32 s
|
||||
PCEResponseTimeout -> PROXY $ BROKER TIMEOUT
|
||||
PCENetworkError -> PROXY $ BROKER NETWORK
|
||||
PCEIncompatibleHost -> PROXY $ BROKER HOST
|
||||
PCETransportError t -> PROXY $ BROKER $ TRANSPORT t
|
||||
PCECryptoError _ -> CRYPTO
|
||||
PCEIOError _ -> INTERNAL
|
||||
|
||||
-- | Create a new SMP queue.
|
||||
@@ -699,21 +702,18 @@ deleteSMPQueues :: SMPClient -> NonEmpty (RcvPrivateAuthKey, RecipientId) -> IO
|
||||
deleteSMPQueues = okSMPCommands DEL
|
||||
{-# INLINE deleteSMPQueues #-}
|
||||
|
||||
-- TODO picture
|
||||
|
||||
-- send PRXY :: SMPServer -> Maybe BasicAuth -> Command Sender
|
||||
-- receives PKEY :: SessionId -> X.CertificateChain -> X.SignedExact X.PubKey -> BrokerMsg
|
||||
connectSMPProxiedRelay :: SMPClient -> SMPServer -> Maybe BasicAuth -> ExceptT SMPClientError IO ProxiedRelay
|
||||
connectSMPProxiedRelay c relayServ@ProtocolServer {keyHash = C.KeyHash kh} proxyAuth =
|
||||
sendSMPCommand c Nothing "" (PRXY relayServ proxyAuth) >>= \case
|
||||
PKEY sId vr (chain, key) -> do
|
||||
PKEY sId vr (chain, key) ->
|
||||
case supportedClientSMPRelayVRange `compatibleVersion` vr of
|
||||
Nothing -> throwE PCEIncompatibleHost -- TODO different error
|
||||
Just (Compatible v) -> liftEitherWith x509Error $ ProxiedRelay sId v <$> validateRelay chain key
|
||||
Nothing -> throwE $ relayErr VERSION
|
||||
Just (Compatible v) -> liftEitherWith (const $ relayErr IDENTITY) $ ProxiedRelay sId v <$> validateRelay chain key
|
||||
r -> throwE . PCEUnexpectedResponse $ bshow r
|
||||
where
|
||||
x509Error :: String -> SMPClientError
|
||||
x509Error _msg = PCEResponseError $ error "TODO: x509 error" -- TODO different error
|
||||
relayErr = PCEProtocolError . PROXY . BROKER . TRANSPORT . TEHandshake
|
||||
validateRelay :: X.CertificateChain -> X.SignedExact X.PubKey -> Either String C.PublicKeyX25519
|
||||
validateRelay (X.CertificateChain cert) exact = do
|
||||
serverKey <- case cert of
|
||||
@@ -730,10 +730,53 @@ data ProxiedRelay = ProxiedRelay
|
||||
prServerKey :: C.PublicKeyX25519
|
||||
}
|
||||
|
||||
data ProxyClientError
|
||||
= -- | protocol error response from proxy
|
||||
ProxyProtocolError ErrorType
|
||||
| -- | unexpexted response
|
||||
ProxyUnexpectedResponse String
|
||||
| -- | error between proxy and server
|
||||
ProxyResponseError ErrorType
|
||||
deriving (Eq, Show, Exception)
|
||||
|
||||
instance StrEncoding ProxyClientError where
|
||||
strEncode = \case
|
||||
ProxyProtocolError e -> "PROTOCOL " <> strEncode e
|
||||
ProxyUnexpectedResponse s -> "UNEXPECTED " <> B.pack s
|
||||
ProxyResponseError e -> "SYNTAX " <> strEncode e
|
||||
strP =
|
||||
A.takeTill (== ' ') >>= \case
|
||||
"PROTOCOL" -> ProxyProtocolError <$> _strP
|
||||
"UNEXPECTED" -> ProxyUnexpectedResponse . B.unpack <$> (A.space *> A.takeByteString)
|
||||
"SYNTAX" -> ProxyResponseError <$> _strP
|
||||
_ -> fail "bad ProxyClientError"
|
||||
|
||||
-- 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
|
||||
|
||||
-- When client sends message via proxy, there may be one successful scenario and 9 error scenarios
|
||||
-- as shown below (WTF stands for unexpected response, ??? for response that failed to parse).
|
||||
-- client proxy relay proxy client
|
||||
-- 0) PFWD(SEND) -> RFWD -> RRES -> PRES(OK) -> ok
|
||||
-- 1) PFWD(SEND) -> RFWD -> RRES -> PRES(ERR) -> PCEProtocolError - business logic error for client
|
||||
-- 2) PFWD(SEND) -> RFWD -> RRES -> PRES(WTF) -> PCEUnexpectedReponse - relay/client protocol logic error
|
||||
-- 3) PFWD(SEND) -> RFWD -> RRES -> PRES(???) -> PCEResponseError - relay/client syntax error
|
||||
-- 4) PFWD(SEND) -> RFWD -> ERR -> ERR PROXY PROTOCOL -> ProxyProtocolError - proxy/relay business logic error
|
||||
-- 5) PFWD(SEND) -> RFWD -> WTF -> ERR PROXY $ BROKER (UNEXPECTED s) -> ProxyProtocolError - proxy/relay protocol logic
|
||||
-- 6) PFWD(SEND) -> RFWD -> ??? -> ERR PROXY $ BROKER (RESPONSE s) -> ProxyProtocolError - - proxy/relay syntax
|
||||
-- 7) PFWD(SEND) -> ERR -> ProxyProtocolError - client/proxy business logic
|
||||
-- 8) PFWD(SEND) -> WTF -> ProxyUnexpectedResponse - client/proxy protocol logic
|
||||
-- 9) PFWD(SEND) -> ??? -> ProxyResponseError - client/proxy syntax
|
||||
--
|
||||
-- We report as proxySMPMessage error (ExceptT error) the errors of two kinds:
|
||||
-- - protocol errors from the destination relay wrapped in PRES - to simplify processing of AUTH and QUOTA errors, in this case proxy is "transparent" for such errors (PCEProtocolError, PCEUnexpectedResponse, PCEResponseError)
|
||||
-- - other response/transport/connection errors from the client connected to proxy itself
|
||||
-- Other errors are reported in the function result as `Either ProxiedRelayError ()`, including
|
||||
-- - protocol errors from the client connected to proxy in ProxyClientError (PCEProtocolError, PCEUnexpectedResponse, PCEResponseError)
|
||||
-- - other errors from the client running on proxy and connected to relay in PREProxiedRelayError
|
||||
|
||||
proxySMPMessage ::
|
||||
SMPClient ->
|
||||
-- proxy session from PKEY
|
||||
@@ -743,7 +786,7 @@ proxySMPMessage ::
|
||||
SenderId ->
|
||||
MsgFlags ->
|
||||
MsgBody ->
|
||||
ExceptT SMPClientError IO ()
|
||||
ExceptT SMPClientError IO (Either ProxyClientError ())
|
||||
-- TODO use version
|
||||
proxySMPMessage c@ProtocolClient {thParams = proxyThParams, client_ = PClient {clientCorrId = g}} (ProxiedRelay sessionId _v serverKey) spKey sId flags msg = do
|
||||
-- prepare params
|
||||
@@ -756,24 +799,32 @@ proxySMPMessage c@ProtocolClient {thParams = proxyThParams, client_ = PClient {c
|
||||
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?
|
||||
[] -> throwE $ PCETransportError TELargeMsg
|
||||
TBError e _ : _ -> throwE $ PCETransportError e
|
||||
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
|
||||
-- proxy interaction errors are wrapped
|
||||
tryE (sendProtocolCommand_ c (Just nonce) Nothing sessionId (Cmd SProxiedClient (PFWD cmdPubKey et))) >>= \case
|
||||
Right r -> case r of
|
||||
PRES (EncResponse er) -> do
|
||||
-- server interaction errors are thrown directly
|
||||
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, cmd)) -> case cmd of
|
||||
Right OK -> pure $ Right ()
|
||||
Right (ERR e) -> throwE $ PCEProtocolError e -- this is the error from the destination relay
|
||||
Right e -> throwE $ PCEUnexpectedResponse $ B.take 32 $ bshow e
|
||||
Left e -> throwE $ PCEResponseError e
|
||||
_ -> throwE $ PCETransportError TEBadBlock
|
||||
ERR e -> pure . Left $ ProxyProtocolError e -- this will not happen, this error is returned via Left
|
||||
_ -> pure . Left $ ProxyUnexpectedResponse $ take 32 $ show r
|
||||
Left e -> case e of
|
||||
PCEProtocolError e' -> pure . Left $ ProxyProtocolError e'
|
||||
PCEUnexpectedResponse r -> pure . Left $ ProxyUnexpectedResponse $ B.unpack r
|
||||
PCEResponseError e' -> pure . Left $ ProxyResponseError e'
|
||||
_ -> throwE e
|
||||
|
||||
-- this method is used in the proxy
|
||||
-- sends RFWD :: EncFwdTransmission -> Command Sender
|
||||
@@ -783,8 +834,8 @@ forwardSMPMessage :: SMPClient -> CorrId -> C.PublicKeyX25519 -> EncTransmission
|
||||
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 {sessSecret} -> maybe (throwError $ PCEProtocolError INTERNAL) pure sessSecret
|
||||
Nothing -> throwError $ PCETransportError TENoServerAuth
|
||||
Just THAuthClient {sessSecret} -> maybe (throwError $ PCETransportError TENoServerAuth) pure sessSecret
|
||||
nonce <- liftIO . atomically $ C.randomCbNonce g
|
||||
-- wrap
|
||||
let fwdT = FwdTransmission {fwdCorrId, fwdKey, fwdTransmission}
|
||||
@@ -796,7 +847,7 @@ forwardSMPMessage c@ProtocolClient {thParams, client_ = PClient {clientCorrId =
|
||||
r' <- liftEitherWith PCECryptoError $ C.cbDecryptNoPad sessSecret (C.reverseNonce nonce) efr
|
||||
FwdResponse {fwdCorrId = _, fwdResponse} <- liftEitherWith (const $ PCEResponseError BLOCK) $ smpDecode r'
|
||||
pure fwdResponse
|
||||
r -> throwE . PCEUnexpectedResponse $ bshow r
|
||||
r -> throwE . PCEUnexpectedResponse $ B.take 32 $ bshow r
|
||||
|
||||
okSMPCommand :: PartyI p => Command p -> SMPClient -> C.APrivateAuthKey -> QueueId -> ExceptT SMPClientError IO ()
|
||||
okSMPCommand cmd c pKey qId =
|
||||
@@ -933,3 +984,5 @@ $(J.deriveJSON (enumJSON $ dropPrefix "TSM") ''TransportSessionMode)
|
||||
$(J.deriveJSON (enumJSON $ dropPrefix "SPM") ''SMPProxyMode)
|
||||
|
||||
$(J.deriveJSON defaultJSON ''NetworkConfig)
|
||||
|
||||
$(J.deriveJSON (enumJSON $ dropPrefix "Proxy") ''ProxyClientError)
|
||||
|
||||
@@ -66,6 +66,7 @@ module Simplex.Messaging.Protocol
|
||||
ErrorType (..),
|
||||
CommandError (..),
|
||||
ProxyError (..),
|
||||
BrokerErrorType (..),
|
||||
Transmission,
|
||||
TransmissionAuth (..),
|
||||
SignedTransmission,
|
||||
@@ -176,6 +177,7 @@ module Simplex.Messaging.Protocol
|
||||
where
|
||||
|
||||
import Control.Applicative (optional, (<|>))
|
||||
import Control.Exception (Exception)
|
||||
import Control.Monad
|
||||
import Control.Monad.Except
|
||||
import Data.Aeson (FromJSON (..), ToJSON (..))
|
||||
@@ -194,8 +196,6 @@ import Data.List.NonEmpty (NonEmpty (..))
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Data.Maybe (isJust, isNothing)
|
||||
import Data.String
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Data.Time.Clock.System (SystemTime (..))
|
||||
import Data.Type.Equality
|
||||
import Data.Word (Word16)
|
||||
@@ -211,7 +211,7 @@ import Simplex.Messaging.Parsers
|
||||
import Simplex.Messaging.ServiceScheme
|
||||
import Simplex.Messaging.Transport
|
||||
import Simplex.Messaging.Transport.Client (TransportHost, TransportHosts (..))
|
||||
import Simplex.Messaging.Util (bshow, eitherToMaybe, safeDecodeUtf8, (<$?>))
|
||||
import Simplex.Messaging.Util (bshow, eitherToMaybe, (<$?>))
|
||||
import Simplex.Messaging.Version
|
||||
import Simplex.Messaging.Version.Internal
|
||||
|
||||
@@ -1144,6 +1144,8 @@ data ErrorType
|
||||
PROXY {proxyErr :: ProxyError}
|
||||
| -- | command authorization error - bad signature or non-existing SMP queue
|
||||
AUTH
|
||||
| -- | encryption/decryption error in proxy protocol
|
||||
CRYPTO
|
||||
| -- | SMP queue capacity is exceeded on the server
|
||||
QUOTA
|
||||
| -- | ACK command is sent without message to be acknowledged
|
||||
@@ -1186,19 +1188,32 @@ data CommandError
|
||||
|
||||
data ProxyError
|
||||
= -- | Correctly parsed SMP server ERR response.
|
||||
-- This error is forwarded to the agent client as `ERR SMP err`.
|
||||
-- This error is forwarded to the agent client as AgentErrorType `ERR PROXY PROTOCOL err`.
|
||||
PROTOCOL {protocolErr :: ErrorType}
|
||||
| -- | Invalid server response that failed to parse.
|
||||
-- Forwarded to the agent client as `ERR BROKER RESPONSE`.
|
||||
RESPONSE {responseErr :: ErrorType}
|
||||
| UNEXPECTED {unexpectedResponse :: String} -- 'String' for using derived JSON and Arbitrary instances
|
||||
| TIMEOUT
|
||||
| NETWORK
|
||||
| BAD_HOST
|
||||
| NO_SESSION
|
||||
| TRANSPORT {transportErr :: TransportError}
|
||||
| -- | destination server error
|
||||
BROKER {brokerErr :: BrokerErrorType}
|
||||
| -- | basic auth provided to proxy is invalid
|
||||
BASIC_AUTH
|
||||
| -- no destination server error
|
||||
NO_SESSION
|
||||
deriving (Eq, Read, Show)
|
||||
|
||||
-- | SMP server errors.
|
||||
data BrokerErrorType
|
||||
= -- | invalid server response (failed to parse)
|
||||
RESPONSE {respErr :: String}
|
||||
| -- | unexpected response
|
||||
UNEXPECTED {respErr :: String}
|
||||
| -- | network error
|
||||
NETWORK
|
||||
| -- | no compatible server host (e.g. onion when public is required, or vice versa)
|
||||
HOST
|
||||
| -- | handshake or other transport error
|
||||
TRANSPORT {transportErr :: TransportError}
|
||||
| -- | command response timeout
|
||||
TIMEOUT
|
||||
deriving (Eq, Read, Show, Exception)
|
||||
|
||||
-- | SMP transmission parser.
|
||||
transmissionP :: THandleParams v p -> Parser RawTransmission
|
||||
transmissionP THandleParams {sessionId, implySessId} = do
|
||||
@@ -1423,6 +1438,7 @@ instance Encoding ErrorType where
|
||||
CMD err -> "CMD " <> smpEncode err
|
||||
PROXY err -> "PROXY " <> smpEncode err
|
||||
AUTH -> "AUTH"
|
||||
CRYPTO -> "CRYPTO"
|
||||
QUOTA -> "QUOTA"
|
||||
EXPIRED -> "EXPIRED"
|
||||
NO_MSG -> "NO_MSG"
|
||||
@@ -1437,13 +1453,14 @@ instance Encoding ErrorType where
|
||||
"CMD" -> CMD <$> _smpP
|
||||
"PROXY" -> PROXY <$> _smpP
|
||||
"AUTH" -> pure AUTH
|
||||
"CRYPTO" -> pure CRYPTO
|
||||
"QUOTA" -> pure QUOTA
|
||||
"EXPIRED" -> pure EXPIRED
|
||||
"NO_MSG" -> pure NO_MSG
|
||||
"LARGE_MSG" -> pure LARGE_MSG
|
||||
"INTERNAL" -> pure INTERNAL
|
||||
"DUPLICATE_" -> pure DUPLICATE_
|
||||
_ -> fail "bad error type"
|
||||
_ -> fail "bad ErrorType"
|
||||
|
||||
instance Encoding CommandError where
|
||||
smpEncode e = case e of
|
||||
@@ -1462,45 +1479,71 @@ instance Encoding CommandError where
|
||||
"HAS_AUTH" -> pure HAS_AUTH
|
||||
"NO_ENTITY" -> pure NO_ENTITY
|
||||
"NO_QUEUE" -> pure NO_ENTITY -- for backward compatibility
|
||||
_ -> fail "bad command error type"
|
||||
_ -> fail "bad CommandError"
|
||||
|
||||
instance Encoding ProxyError where
|
||||
smpEncode e = case e of
|
||||
PROTOCOL et -> "PROTOCOL " <> smpEncode et
|
||||
RESPONSE et -> "RESPONSE " <> smpEncode et
|
||||
UNEXPECTED s -> "UNEXPECTED " <> smpEncode (encodeUtf8 $ T.pack s)
|
||||
TIMEOUT -> "TIMEOUT"
|
||||
NETWORK -> "NETWORK"
|
||||
BAD_HOST -> "BAD_HOST"
|
||||
smpEncode = \case
|
||||
PROTOCOL e -> "PROTOCOL " <> smpEncode e
|
||||
BROKER e -> "BROKER " <> smpEncode e
|
||||
BASIC_AUTH -> "BASIC_AUTH"
|
||||
NO_SESSION -> "NO_SESSION"
|
||||
TRANSPORT t -> "TRANSPORT " <> serializeTransportError t
|
||||
smpP =
|
||||
A.takeTill (== ' ') >>= \case
|
||||
"PROTOCOL" -> PROTOCOL <$> _smpP
|
||||
"RESPONSE" -> RESPONSE <$> _smpP
|
||||
"UNEXPECTED" -> UNEXPECTED . (T.unpack . safeDecodeUtf8) <$> _smpP
|
||||
"TIMEOUT" -> pure TIMEOUT
|
||||
"NETWORK" -> pure NETWORK
|
||||
"BAD_HOST" -> pure BAD_HOST
|
||||
"BROKER" -> BROKER <$> _smpP
|
||||
"BASIC_AUTH" -> pure BASIC_AUTH
|
||||
"NO_SESSION" -> pure NO_SESSION
|
||||
"TRANSPORT" -> TRANSPORT <$> (A.space *> transportErrorP)
|
||||
_ -> fail "bad command error type"
|
||||
_ -> fail "bad ProxyError"
|
||||
|
||||
instance StrEncoding ProxyError where
|
||||
strEncode = \case
|
||||
PROTOCOL et -> "PROTOCOL " <> strEncode et
|
||||
RESPONSE et -> "RESPONSE " <> strEncode et
|
||||
UNEXPECTED "" -> "UNEXPECTED" -- Arbitrary instance generates empty strings which String instance can't handle
|
||||
UNEXPECTED s -> "UNEXPECTED " <> strEncode s
|
||||
TRANSPORT t -> "TRANSPORT " <> serializeTransportError t
|
||||
e -> bshow e
|
||||
PROTOCOL e -> "PROTOCOL " <> strEncode e
|
||||
BROKER e -> "BROKER " <> strEncode e
|
||||
BASIC_AUTH -> "BASIC_AUTH"
|
||||
NO_SESSION -> "NO_SESSION"
|
||||
strP =
|
||||
"PROTOCOL " *> (PROTOCOL <$> strP)
|
||||
<|> "RESPONSE " *> (RESPONSE <$> strP)
|
||||
<|> "UNEXPECTED " *> (UNEXPECTED <$> strP)
|
||||
<|> "UNEXPECTED" $> UNEXPECTED ""
|
||||
<|> "TRANSPORT " *> (TRANSPORT <$> transportErrorP)
|
||||
<|> parseRead1
|
||||
A.takeTill (== ' ') >>= \case
|
||||
"PROTOCOL" -> PROTOCOL <$> _strP
|
||||
"BROKER" -> BROKER <$> _strP
|
||||
"BASIC_AUTH" -> pure BASIC_AUTH
|
||||
"NO_SESSION" -> pure NO_SESSION
|
||||
_ -> fail "bad ProxyError"
|
||||
|
||||
instance Encoding BrokerErrorType where
|
||||
smpEncode = \case
|
||||
RESPONSE e -> "RESPONSE " <> smpEncode e
|
||||
UNEXPECTED e -> "UNEXPECTED " <> smpEncode e
|
||||
TRANSPORT e -> "TRANSPORT " <> serializeTransportError e
|
||||
NETWORK -> "NETWORK"
|
||||
TIMEOUT -> "TIMEOUT"
|
||||
HOST -> "HOST"
|
||||
smpP =
|
||||
A.takeTill (== ' ') >>= \case
|
||||
"RESPONSE" -> RESPONSE <$> _smpP
|
||||
"UNEXPECTED" -> UNEXPECTED <$> _smpP
|
||||
"TRANSPORT" -> TRANSPORT <$> (A.space *> transportErrorP)
|
||||
"NETWORK" -> pure NETWORK
|
||||
"TIMEOUT" -> pure TIMEOUT
|
||||
"HOST" -> pure HOST
|
||||
_ -> fail "bad BrokerErrorType"
|
||||
|
||||
instance StrEncoding BrokerErrorType where
|
||||
strEncode = \case
|
||||
RESPONSE e -> "RESPONSE " <> strEncode e
|
||||
UNEXPECTED e -> "UNEXPECTED " <> strEncode e
|
||||
TRANSPORT e -> "TRANSPORT " <> serializeTransportError e
|
||||
NETWORK -> "NETWORK"
|
||||
TIMEOUT -> "TIMEOUT"
|
||||
HOST -> "HOST"
|
||||
strP =
|
||||
A.takeTill (== ' ') >>= \case
|
||||
"RESPONSE" -> RESPONSE <$> _strP
|
||||
"UNEXPECTED" -> UNEXPECTED <$> _strP
|
||||
"TRANSPORT" -> TRANSPORT <$> (A.space *> transportErrorP)
|
||||
"NETWORK" -> pure NETWORK
|
||||
"TIMEOUT" -> pure TIMEOUT
|
||||
"HOST" -> pure HOST
|
||||
_ -> fail "bad BrokerErrorType"
|
||||
|
||||
-- | Send signed SMP transmission to TCP transport.
|
||||
tPut :: Transport c => THandle v c p -> NonEmpty (Either TransportError SentRawTransmission) -> IO [Either TransportError ()]
|
||||
@@ -1639,5 +1682,7 @@ $(J.deriveJSON defaultJSON ''MsgFlags)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON id) ''CommandError)
|
||||
|
||||
$(J.deriveJSON (sumTypeJSON id) ''BrokerErrorType)
|
||||
|
||||
-- run deriveJSON in one TH splice to allow mutual instance
|
||||
$(concat <$> mapM @[] (J.deriveJSON (sumTypeJSON id)) [''ProxyError, ''ErrorType])
|
||||
|
||||
@@ -54,7 +54,7 @@ import Data.Functor (($>))
|
||||
import Data.Int (Int64)
|
||||
import qualified Data.IntMap.Strict as IM
|
||||
import Data.List (intercalate)
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import qualified Data.Map.Strict as M
|
||||
import Data.Maybe (isNothing)
|
||||
@@ -68,7 +68,7 @@ import GHC.Stats (getRTSStats)
|
||||
import GHC.TypeLits (KnownNat)
|
||||
import Network.Socket (ServiceName, Socket, socketToHandle)
|
||||
import Simplex.Messaging.Agent.Lock
|
||||
import Simplex.Messaging.Client (ProtocolClient (thParams), forwardSMPMessage, smpProxyError)
|
||||
import Simplex.Messaging.Client (ProtocolClient (thParams), ProtocolClientError (..), forwardSMPMessage, smpProxyError)
|
||||
import Simplex.Messaging.Client.Agent (SMPClientAgent (..), SMPClientAgentEvent (..), getSMPServerClient', lookupSMPServerClient)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Encoding
|
||||
@@ -616,28 +616,27 @@ client thParams' clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessi
|
||||
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)
|
||||
PRXY srv auth -> ifM allowProxy getRelay (pure $ ERR $ PROXY BASIC_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)
|
||||
liftIO $ proxyResp <$> runExceptT (getSMPServerClient' smpAgent srv) `catch` (pure . Left . PCEIOError)
|
||||
where
|
||||
proxyResp = \case
|
||||
Left err -> ERR $ smpProxyError err
|
||||
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 TENoServerAuth)
|
||||
Left err -> ERR $ smpProxyError err
|
||||
Nothing -> ERR . PROXY . BROKER $ TRANSPORT TENoServerAuth
|
||||
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)
|
||||
Just smp -> liftIO $ either (ERR . smpProxyError) PRES <$> runExceptT (forwardSMPMessage smp corrId pubKey encBlock) `catchError` (pure . Left . PCEIOError)
|
||||
Nothing -> pure $ ERR $ PROXY NO_SESSION
|
||||
processCommand :: (Maybe QueueRec, Transmission Cmd) -> M (Either (Transmission (Command 'ProxiedClient)) (Transmission BrokerMsg))
|
||||
processCommand (qr_, (corrId, queueId, cmd)) = do
|
||||
@@ -916,50 +915,48 @@ client thParams' clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessi
|
||||
pure . (cbNonce,) $ fromRight "" encNMsgMeta
|
||||
|
||||
processForwardedCommand :: EncFwdTransmission -> M BrokerMsg
|
||||
processForwardedCommand (EncFwdTransmission s) = fmap (either id id) . runExceptT $ do
|
||||
-- TODO error
|
||||
THAuthServer {serverPrivKey, sessSecret'} <- maybe (throwError $ ERR INTERNAL) pure thAuth
|
||||
sessSecret <- maybe (throwError $ ERR INTERNAL) pure sessSecret'
|
||||
processForwardedCommand (EncFwdTransmission s) = fmap (either ERR id) . runExceptT $ do
|
||||
THAuthServer {serverPrivKey, sessSecret'} <- maybe (throwE noRelayAuth) pure (thAuth thParams')
|
||||
sessSecret <- maybe (throwE noRelayAuth) pure sessSecret'
|
||||
let proxyNonce = C.cbNonce $ bs corrId
|
||||
-- TODO error
|
||||
s' <- liftEitherWith internalErr $ C.cbDecryptNoPad 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
|
||||
s' <- liftEitherWith (const CRYPTO) $ C.cbDecryptNoPad sessSecret proxyNonce s
|
||||
FwdTransmission {fwdCorrId, fwdKey, fwdTransmission = EncTransmission et} <- liftEitherWith (const $ CMD SYNTAX) $ smpDecode s'
|
||||
let clientSecret = C.dh' fwdKey serverPrivKey
|
||||
clientNonce = C.cbNonce $ bs fwdCorrId
|
||||
b <- liftEitherWith internalErr $ C.cbDecrypt clientSecret clientNonce et
|
||||
b <- liftEitherWith (const CRYPTO) $ C.cbDecrypt clientSecret clientNonce et
|
||||
-- only allowing single forwarded transactions
|
||||
let t' = tDecodeParseValidate thParams' $ L.head $ tParse thParams' b
|
||||
clntThAuth = Just $ THAuthServer {serverPrivKey, sessSecret' = Just clientSecret}
|
||||
-- TODO error
|
||||
t' <- case tParse thParams' b of
|
||||
t :| [] -> pure $ tDecodeParseValidate thParams' t
|
||||
_ -> throwE BLOCK
|
||||
let clntThAuth = Just $ THAuthServer {serverPrivKey, sessSecret' = Just clientSecret}
|
||||
-- process forwarded SEND
|
||||
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'')
|
||||
|
||||
Right t''@(_, (corrId', entId', cmd')) -> case cmd' of
|
||||
Cmd SSender SEND {} ->
|
||||
-- Left will not be returned by processCommand, as only SEND command is allowed
|
||||
fromRight (corrId', entId', ERR INTERNAL) <$> lift (processCommand t'')
|
||||
_ ->
|
||||
pure (corrId', entId', ERR $ CMD PROHIBITED)
|
||||
-- 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
|
||||
[] -> throwE INTERNAL -- at least 1 item is guaranteed from NonEmpty/Right
|
||||
TBError _ _ : _ -> throwE BLOCK
|
||||
TBTransmission b' _ : _ -> pure b'
|
||||
TBTransmissions b' _ _ : _ -> pure b'
|
||||
-- encrypt to client
|
||||
r2 <- liftEitherWith internalErr $ EncResponse <$> C.cbEncrypt clientSecret (C.reverseNonce clientNonce) r' paddedProxiedMsgLength
|
||||
r2 <- liftEitherWith (const BLOCK) $ EncResponse <$> C.cbEncrypt clientSecret (C.reverseNonce clientNonce) r' paddedProxiedMsgLength
|
||||
-- encrypt to proxy
|
||||
let fr = FwdResponse {fwdCorrId, fwdResponse = r2}
|
||||
r3 = EncFwdResponse $ C.cbEncryptNoPad sessSecret (C.reverseNonce proxyNonce) (smpEncode fr)
|
||||
pure $ RRES r3
|
||||
where
|
||||
internalErr _ = ERR INTERNAL -- TODO errors
|
||||
THandleParams {thAuth} = thParams'
|
||||
noRelayAuth = PROXY $ BROKER $ TRANSPORT TENoServerAuth
|
||||
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
|
||||
|
||||
@@ -6,17 +6,15 @@
|
||||
|
||||
module CoreTests.ProtocolErrorTests where
|
||||
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import GHC.Generics (Generic)
|
||||
import Generic.Random (genericArbitraryU)
|
||||
import Simplex.FileTransfer.Transport (XFTPErrorType (..))
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import qualified Simplex.Messaging.Agent.Protocol as Agent
|
||||
import Simplex.Messaging.Client (ProxyClientError (..))
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Protocol (CommandError (..), ErrorType (..), ProxyError (..))
|
||||
import Simplex.Messaging.Protocol (CommandError (..), ErrorType (..))
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.Transport (HandshakeError (..), TransportError (..))
|
||||
import Simplex.RemoteControl.Types (RCErrorType (..))
|
||||
@@ -27,21 +25,32 @@ import Test.QuickCheck
|
||||
protocolErrorTests :: Spec
|
||||
protocolErrorTests = modifyMaxSuccess (const 1000) $ do
|
||||
describe "errors parsing / serializing" $ do
|
||||
it "should parse SMP protocol errors" . property $ \(err :: ErrorType) ->
|
||||
it "should parse SMP protocol errors" . property . forAll possibleErrorType $ \err ->
|
||||
smpDecode (smpEncode err) == Right err
|
||||
it "should parse SMP agent errors" . property . forAll possible $ \err ->
|
||||
it "should parse SMP agent errors" . property . forAll possibleAgentErrorType $ \err ->
|
||||
strDecode (strEncode err) == Right err
|
||||
where
|
||||
possible :: Gen AgentErrorType
|
||||
possible =
|
||||
possibleErrorType :: Gen ErrorType
|
||||
possibleErrorType = arbitrary >>= \e -> if skipErrorType e then discard else pure e
|
||||
possibleAgentErrorType :: Gen AgentErrorType
|
||||
possibleAgentErrorType =
|
||||
arbitrary >>= \case
|
||||
BROKER srv (Agent.RESPONSE e) | hasSpaces srv || hasSpaces e -> discard
|
||||
BROKER srv _ | hasSpaces srv -> discard
|
||||
SMP (PROXY (SMP.UNEXPECTED s)) | hasUnicode s -> discard
|
||||
NTF (PROXY (SMP.UNEXPECTED s)) | hasUnicode s -> discard
|
||||
BROKER srv _ | skip srv -> discard
|
||||
BROKER _ (RESPONSE e) | skip e -> discard
|
||||
BROKER _ (UNEXPECTED e) | skip e -> discard
|
||||
SMP e | skipErrorType e -> discard
|
||||
NTF e | skipErrorType e -> discard
|
||||
Agent.PROXY pxy srv _ | skip pxy || skip srv -> discard
|
||||
Agent.PROXY _ _ (ProxyProtocolError e) | skipErrorType e -> discard
|
||||
Agent.PROXY _ _ (ProxyUnexpectedResponse e) | skip e -> discard
|
||||
Agent.PROXY _ _ (ProxyResponseError e) | skipErrorType e -> discard
|
||||
ok -> pure ok
|
||||
hasSpaces s = ' ' `B.elem` encodeUtf8 (T.pack s)
|
||||
hasUnicode = any (>= '\255')
|
||||
skip s = null s || any (\c -> c <= ' ' || c >= '\255') s
|
||||
skipErrorType = \case
|
||||
SMP.PROXY (SMP.PROTOCOL e) -> skipErrorType e
|
||||
SMP.PROXY (SMP.BROKER (UNEXPECTED s)) -> skip s
|
||||
SMP.PROXY (SMP.BROKER (RESPONSE s)) -> skip s
|
||||
_ -> False
|
||||
|
||||
deriving instance Generic AgentErrorType
|
||||
|
||||
@@ -49,6 +58,8 @@ deriving instance Generic CommandErrorType
|
||||
|
||||
deriving instance Generic ConnectionErrorType
|
||||
|
||||
deriving instance Generic ProxyClientError
|
||||
|
||||
deriving instance Generic BrokerErrorType
|
||||
|
||||
deriving instance Generic SMPAgentError
|
||||
@@ -59,7 +70,7 @@ deriving instance Generic ErrorType
|
||||
|
||||
deriving instance Generic CommandError
|
||||
|
||||
deriving instance Generic ProxyError
|
||||
deriving instance Generic SMP.ProxyError
|
||||
|
||||
deriving instance Generic TransportError
|
||||
|
||||
@@ -75,6 +86,8 @@ instance Arbitrary CommandErrorType where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary ConnectionErrorType where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary ProxyClientError where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary BrokerErrorType where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary SMPAgentError where arbitrary = genericArbitraryU
|
||||
@@ -85,7 +98,7 @@ instance Arbitrary ErrorType where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary CommandError where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary ProxyError where arbitrary = genericArbitraryU
|
||||
instance Arbitrary SMP.ProxyError where arbitrary = genericArbitraryU
|
||||
|
||||
instance Arbitrary TransportError where arbitrary = genericArbitraryU
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ deliverMessageViaProxy proxyServ relayServ alg msg msg' = do
|
||||
-- get proxy session
|
||||
sess <- connectSMPProxiedRelay pc relayServ (Just "correct")
|
||||
-- send via proxy to unsecured queue
|
||||
proxySMPMessage pc sess Nothing sndId noMsgFlags msg
|
||||
Right () <- proxySMPMessage pc sess Nothing sndId noMsgFlags msg
|
||||
-- receive 1
|
||||
(_tSess, _v, _sid, _isResp, _entId, SMP.MSG RcvMessage {msgId, msgBody = EncRcvMsgBody encBody}) <- atomically $ readTBQueue msgQ
|
||||
liftIO $ dec msgId encBody `shouldBe` Right msg
|
||||
@@ -122,7 +122,7 @@ deliverMessageViaProxy proxyServ relayServ alg msg msg' = do
|
||||
(sPub, sPriv) <- atomically $ C.generateAuthKeyPair alg g
|
||||
secureSMPQueue rc rPriv rcvId sPub
|
||||
-- send via proxy to secured queue
|
||||
proxySMPMessage pc sess (Just sPriv) sndId noMsgFlags msg'
|
||||
Right () <- proxySMPMessage pc sess (Just sPriv) sndId noMsgFlags msg'
|
||||
-- receive 2
|
||||
(_tSess, _v, _sid, _isResp, _entId, SMP.MSG RcvMessage {msgId = msgId', msgBody = EncRcvMsgBody encBody'}) <- atomically $ readTBQueue msgQ
|
||||
liftIO $ dec msgId' encBody' `shouldBe` Right msg'
|
||||
@@ -171,14 +171,14 @@ testNoProxy = do
|
||||
withSmpServerConfigOn (transport @TLS) cfg testPort2 $ \_ -> do
|
||||
testSMPClient_ "127.0.0.1" testPort2 proxyVRange $ \(th :: THandleSMP TLS 'TClient) -> do
|
||||
(_, _, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer Nothing)
|
||||
reply `shouldBe` Right (SMP.ERR SMP.AUTH)
|
||||
reply `shouldBe` Right (SMP.ERR $ SMP.PROXY SMP.BASIC_AUTH)
|
||||
|
||||
testProxyAuth :: IO ()
|
||||
testProxyAuth = do
|
||||
withSmpServerConfigOn (transport @TLS) proxyCfgAuth testPort $ \_ -> 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")
|
||||
reply `shouldBe` Right (SMP.ERR SMP.AUTH)
|
||||
reply `shouldBe` Right (SMP.ERR $ SMP.PROXY SMP.BASIC_AUTH)
|
||||
where
|
||||
proxyCfgAuth = proxyCfg {newQueueBasicAuth = Just "correct"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user