move SMP server from agent commands NEW/JOIN to agent config (#129)

* move SMP server from agent commands NEW/JOIN to agent config

* fix SMPServer parser

* update agent protocol - server management

* enable agent test

* agent test with 2 servers

* create reply queue on the configured server

* choose random server

* swap bind
This commit is contained in:
Evgeny Poberezkin
2021-05-06 18:53:34 +01:00
committed by GitHub
parent f09c77cd31
commit 4b9ebbbab2
12 changed files with 140 additions and 88 deletions
+3
View File
@@ -1,8 +1,10 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Logger.Simple
import qualified Data.List.NonEmpty as L
import Simplex.Messaging.Agent (runSMPAgent)
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Client (smpDefaultConfig)
@@ -11,6 +13,7 @@ cfg :: AgentConfig
cfg =
AgentConfig
{ tcpPort = "5224",
smpServers = L.fromList ["localhost:5223#KXNE1m2E1m0lm92WGKet9CL6+lO742Vy5G6nsrkvgs8="],
rsaKeySize = 2048 `div` 8,
connIdBytes = 12,
tbqSize = 16,
+1
View File
@@ -31,6 +31,7 @@ dependencies:
- network == 3.1.*
- network-transport == 0.5.*
- QuickCheck == 2.13.*
- random == 1.1.*
- simple-logger == 0.1.*
- sqlite-simple == 0.4.*
- stm
+11 -5
View File
@@ -4,6 +4,7 @@
- [Abstract](#abstract)
- [SMP agent](#smp-agent)
- [SMP servers management](#smp-servers-management)
- [SMP agent protocol components](#smp-agent-protocol-components)
- [Duplex connection procedure](#duplex-connection-procedure)
- [Communication between SMP agents](#communication-between-smp-agents)
@@ -29,7 +30,7 @@
The purpose of SMP agent protocol is to define the syntax and the semantics of communications between the client and the agent that connects to [SMP](./simplex-messaging.md) servers.
It provides:
- convenient protocol to create and manage a bi-directional (duplex) connection to the users of SMP agents consisting of two separate unidirectional (simplex) SMP queues, abstracting away multiple steps required to establish bi-directional connections.
- convenient protocol to create and manage bi-directional (duplex) connections between the users of SMP agents consisting of two (or more) separate unidirectional (simplex) SMP queues, abstracting away multiple steps required to establish bi-directional connections and any information about the servers location from the users of the protocol.
- management of E2E encryption between SMP agents, generating ephemeral RSA keys for each connection.
- SMP command authentication on SMP servers, generating ephemeral RSA keys for each SMP queue.
- TCP transport handshake and encryption with SMP servers.
@@ -49,6 +50,12 @@ SMP agent is a client-side process or library that communicates via SMP servers
The agent must have a persistent storage to manage the states of known connections and of the client-side information of two SMP queues that each connection consists of, and also the buffer of the most recent messages. The number of the messages that should be stored is implementation specific, depending on the error management approach that the agent implements; at the very least the agent must store the hash and id of the last received message.
## SMP servers management
SMP agent protocol commands do not contain SMP servers that the agent will use to establish the connections between their users. The servers are part of the agent configuration and can be dynamically added and removed by the agent implementation:
- by the client applications via any API that is outside of scope of this protocol.
- by the agents themselves based on servers availability and latency.
## SMP agent protocol components
SMP agent protocol has 3 main parts:
@@ -189,8 +196,7 @@ agentCommand = (userCmd / agentMsg) CRLF
userCmd = newCmd / joinCmd / subscribeCmd / sendCmd / acknowledgeCmd / suspendCmd / deleteCmd
agentMsg = invitation / connected / unsubscribed / message / sent / received / ok / error
newCmd = %s"NEW" SP <smpServer> [SP %s"NO_ACK"] ; `smpServer` is the same as in out-of-band message, see SMP protocol
; response is `invitation` or `error`
newCmd = %s"NEW" [SP %s"NO_ACK"] ; response is `invitation` or `error`
invitation = %s"INV" SP <queueInfo> ; `queueInfo` is the same as in out-of-band message, see SMP protocol
@@ -202,8 +208,8 @@ unsubscribed = %s"END"
; when another agent (or another client of the same agent)
; subscribes to the same SMP queue on the server
joinCmd = %s"JOIN" SP <queueInfo> [replyJoin] [SP %s"NO_ACK"] ; `queueInfo` is the same as in out-of-band message, see SMP protocol
replyJoin = SP (<smpServer> / %s"NO_REPLY") ; reply queue SMP server, by default server from queueInfo is used
joinCmd = %s"JOIN" SP <queueInfo> [SP %s"NO_REPLY"] [SP %s"NO_ACK"]
; `queueInfo` is the same as in out-of-band message, see SMP protocol
; response is `connected` or `error`
suspendCmd = %s"OFF" ; can be sent by either party, response `ok` or `error`
+24 -12
View File
@@ -16,6 +16,7 @@ module Simplex.Messaging.Agent
)
where
import Control.Concurrent.STM (stateTVar)
import Control.Logger.Simple (logInfo, showText)
import Control.Monad.Except
import Control.Monad.IO.Unlift (MonadUnliftIO)
@@ -23,6 +24,8 @@ import Control.Monad.Reader
import Crypto.Random (MonadRandom)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import Data.Time.Clock
@@ -39,6 +42,7 @@ import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.Transport (putLn, runTCPServer)
import Simplex.Messaging.Util (bshow)
import System.IO (Handle)
import System.Random (randomR)
import UnliftIO.Async (race_)
import qualified UnliftIO.Exception as E
import UnliftIO.STM
@@ -51,7 +55,7 @@ runSMPAgentBlocking started cfg@AgentConfig {tcpPort} = runReaderT smpAgent =<<
where
smpAgent :: (MonadUnliftIO m', MonadReader Env m') => m' ()
smpAgent = runTCPServer started tcpPort $ \h -> do
liftIO $ putLn h "Welcome to SMP v0.2.0 agent"
liftIO $ putLn h "Welcome to SMP v0.3.0 agent"
c <- getSMPAgentClient
logConnection c True
race_ (connectClient h c) (runSMPAgentClient c)
@@ -127,7 +131,7 @@ withStore action = do
processCommand :: forall m. AgentMonad m => AgentClient -> SQLiteStore -> ATransmission 'Client -> m ()
processCommand c@AgentClient {sndQ} st (corrId, connAlias, cmd) =
case cmd of
NEW smpServer -> createNewConnection smpServer
NEW -> createNewConnection
JOIN smpQueueInfo replyMode -> joinConnection smpQueueInfo replyMode
SUB -> subscribeConnection connAlias
SUBALL -> subscribeAll
@@ -135,25 +139,32 @@ processCommand c@AgentClient {sndQ} st (corrId, connAlias, cmd) =
OFF -> suspendConnection
DEL -> deleteConnection
where
createNewConnection :: SMPServer -> m ()
createNewConnection server = do
createNewConnection :: m ()
createNewConnection = do
-- TODO create connection alias if not passed
-- make connAlias Maybe?
(rq, qInfo) <- newReceiveQueue c server connAlias
srv <- getSMPServer
(rq, qInfo) <- newReceiveQueue c srv connAlias
withStore $ createRcvConn st rq
respond $ INV qInfo
getSMPServer :: m SMPServer
getSMPServer =
asks (smpServers . config) >>= \case
srv :| [] -> pure srv
servers -> do
gen <- asks randomServer
i <- atomically . stateTVar gen $ randomR (0, L.length servers - 1)
pure $ servers L.!! i
joinConnection :: SMPQueueInfo -> ReplyMode -> m ()
joinConnection qInfo@(SMPQueueInfo srv _ _) replyMode = do
joinConnection qInfo (ReplyMode replyMode) = do
-- TODO create connection alias if not passed
-- make connAlias Maybe?
(sq, senderKey, verifyKey) <- newSendQueue qInfo connAlias
withStore $ createSndConn st sq
connectToSendQueue c st sq senderKey verifyKey
case replyMode of
ReplyOn -> sendReplyQInfo srv sq
ReplyVia srv' -> sendReplyQInfo srv' sq
ReplyOff -> return ()
when (replyMode == On) $ createReplyQueue sq
respond CON
subscribeConnection :: ConnAlias -> m ()
@@ -216,8 +227,9 @@ processCommand c@AgentClient {sndQ} st (corrId, connAlias, cmd) =
removeSubscription c connAlias
delConn
sendReplyQInfo :: SMPServer -> SndQueue -> m ()
sendReplyQInfo srv sq = do
createReplyQueue :: SndQueue -> m ()
createReplyQueue sq = do
srv <- getSMPServer
(rq, qInfo) <- newReceiveQueue c srv connAlias
withStore $ upgradeSndConnToDuplex st connAlias rq
senderTimestamp <- liftIO getCurrentTime
+9 -3
View File
@@ -7,14 +7,18 @@ module Simplex.Messaging.Agent.Env.SQLite where
import Control.Monad.IO.Unlift
import Crypto.Random
import Data.List.NonEmpty (NonEmpty)
import Network.Socket
import Numeric.Natural
import Simplex.Messaging.Agent.Store.SQLite
import Simplex.Messaging.Agent.Transmission (SMPServer)
import Simplex.Messaging.Client
import System.Random (StdGen, newStdGen)
import UnliftIO.STM
data AgentConfig = AgentConfig
{ tcpPort :: ServiceName,
smpServers :: NonEmpty SMPServer,
rsaKeySize :: Int,
connIdBytes :: Int,
tbqSize :: Natural,
@@ -26,15 +30,17 @@ data Env = Env
{ config :: AgentConfig,
idsDrg :: TVar ChaChaDRG,
clientCounter :: TVar Int,
reservedMsgSize :: Int
reservedMsgSize :: Int,
randomServer :: TVar StdGen
}
newSMPAgentEnv :: (MonadUnliftIO m, MonadRandom m) => AgentConfig -> m Env
newSMPAgentEnv config = do
idsDrg <- drgNew >>= newTVarIO
idsDrg <- newTVarIO =<< drgNew
_ <- createSQLiteStore $ dbFile config
clientCounter <- newTVarIO 0
return Env {config, idsDrg, clientCounter, reservedMsgSize}
randomServer <- newTVarIO =<< liftIO newStdGen
return Env {config, idsDrg, clientCounter, reservedMsgSize, randomServer}
where
-- 1st rsaKeySize is used by the RSA signature in each command,
-- 2nd - by encrypted message body header
+16 -17
View File
@@ -23,6 +23,7 @@ import qualified Data.ByteString.Char8 as B
import Data.Functor (($>))
import Data.Int (Int64)
import Data.Kind (Type)
import Data.String (IsString (..))
import Data.Time.Clock (UTCTime)
import Data.Time.ISO8601
import Data.Type.Equality
@@ -75,7 +76,7 @@ data ACmd = forall p. ACmd (SAParty p) (ACommand p)
deriving instance Show ACmd
data ACommand (p :: AParty) where
NEW :: SMPServer -> ACommand Client -- response INV
NEW :: ACommand Client -- response INV
INV :: SMPQueueInfo -> ACommand Agent
JOIN :: SMPQueueInfo -> ReplyMode -> ACommand Client -- response OK
CON :: ACommand Agent -- notification that connection is established
@@ -170,7 +171,7 @@ agentMessageP =
a_msg = do
size :: Int <- A.decimal <* A.endOfLine
A_MSG <$> A.take size <* A.endOfLine
ackMode = " NO_ACK" $> AckMode Off <|> pure (AckMode On)
ackMode = AckMode <$> (" NO_ACK" $> Off <|> pure On)
smpQueueInfoP :: Parser SMPQueueInfo
smpQueueInfoP =
@@ -179,7 +180,7 @@ smpQueueInfoP =
smpServerP :: Parser SMPServer
smpServerP = SMPServer <$> server <*> optional port <*> optional kHash
where
server = B.unpack <$> A.takeTill (A.inClass ":# ")
server = B.unpack <$> A.takeWhile1 (A.notInClass ":# ")
port = A.char ':' *> (B.unpack <$> A.takeWhile1 A.isDigit)
kHash = C.KeyHash <$> (A.char '#' *> base64P)
@@ -207,18 +208,21 @@ data SMPServer = SMPServer
}
deriving (Eq, Ord, Show)
instance IsString SMPServer where
fromString = parseString . parseAll $ smpServerP
type ConnAlias = ByteString
type OtherPartyId = Encoded
data Mode = On | Off deriving (Eq, Show, Read)
data OnOff = On | Off deriving (Eq, Show, Read)
newtype AckMode = AckMode Mode deriving (Eq, Show)
newtype AckMode = AckMode OnOff deriving (Eq, Show)
data SMPQueueInfo = SMPQueueInfo SMPServer SMP.SenderId EncryptionKey
deriving (Eq, Show)
data ReplyMode = ReplyOff | ReplyOn | ReplyVia SMPServer deriving (Eq, Show)
newtype ReplyMode = ReplyMode OnOff deriving (Eq, Show)
type EncryptionKey = C.PublicKey
@@ -294,7 +298,7 @@ instance Arbitrary SMPAgentError where arbitrary = genericArbitraryU
commandP :: Parser ACmd
commandP =
"NEW " *> newCmd
"NEW" $> ACmd SClient NEW
<|> "INV " *> invResp
<|> "JOIN " *> joinCmd
<|> "SUB" $> ACmd SClient SUB
@@ -309,7 +313,6 @@ commandP =
<|> "CON" $> ACmd SAgent CON
<|> "OK" $> ACmd SAgent OK
where
newCmd = ACmd SClient . NEW <$> smpServerP
invResp = ACmd SAgent . INV <$> smpQueueInfoP
joinCmd = ACmd SClient <$> (JOIN <$> smpQueueInfoP <*> replyMode)
sendCmd = ACmd SClient . SEND <$> A.takeByteString
@@ -321,10 +324,7 @@ commandP =
senderMeta <- "S=" *> partyMeta A.decimal
msgBody <- A.takeByteString
return $ ACmd SAgent MSG {recipientMeta, brokerMeta, senderMeta, msgIntegrity, msgBody}
replyMode =
" NO_REPLY" $> ReplyOff
<|> A.space *> (ReplyVia <$> smpServerP)
<|> pure ReplyOn
replyMode = ReplyMode <$> (" NO_REPLY" $> Off <|> pure On)
partyMeta idParser = (,) <$> idParser <* "," <*> tsISO8601P <* A.space
agentError = ACmd SAgent . ERR <$> agentErrorTypeP
@@ -342,7 +342,7 @@ parseCommand = parse commandP $ CMD SYNTAX
serializeCommand :: ACommand p -> ByteString
serializeCommand = \case
NEW srv -> "NEW " <> serializeServer srv
NEW -> "NEW"
INV qInfo -> "INV " <> serializeSmpQueueInfo qInfo
JOIN qInfo rMode -> "JOIN " <> serializeSmpQueueInfo qInfo <> replyMode rMode
SUB -> "SUB"
@@ -367,9 +367,8 @@ serializeCommand = \case
where
replyMode :: ReplyMode -> ByteString
replyMode = \case
ReplyOff -> " NO_REPLY"
ReplyVia srv -> " " <> serializeServer srv
ReplyOn -> ""
ReplyMode Off -> " NO_REPLY"
ReplyMode On -> ""
showTs :: UTCTime -> ByteString
showTs = B.pack . formatISO8601Millis
@@ -433,7 +432,7 @@ tGet party h = liftIO (tGetRaw h) >>= tParseLoadBody
tConnAlias :: ARawTransmission -> ACommand p -> Either AgentErrorType (ACommand p)
tConnAlias (_, connAlias, _) cmd = case cmd of
-- NEW and JOIN have optional connAlias
NEW _ -> Right cmd
NEW -> Right cmd
JOIN _ _ -> Right cmd
-- ERROR response does not always have connAlias
ERR _ -> Right cmd
+2 -3
View File
@@ -244,9 +244,8 @@ sendSMPCommand SMPClient {sndQ, sentCommands, clientCorrId, tcpTimeout} pKey qId
getNextCorrId :: STM CorrId
getNextCorrId = do
i <- (+ 1) <$> readTVar clientCorrId
writeTVar clientCorrId i
return . CorrId $ bshow i
i <- stateTVar clientCorrId $ \i -> (i, i + 1)
pure . CorrId $ bshow i
signTransmission :: ByteString -> ExceptT SMPClientError IO SignedRawTransmission
signTransmission t = case pKey of
+1 -4
View File
@@ -80,7 +80,7 @@ import Data.X509
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Network.Transport.Internal (decodeWord32, encodeWord32)
import Simplex.Messaging.Parsers (base64P, blobFieldParser, parseAll)
import Simplex.Messaging.Parsers (base64P, blobFieldParser, parseAll, parseString)
import Simplex.Messaging.Util (liftEitherError, (<$?>))
newtype PublicKey = PublicKey {rsaPublicKey :: R.PublicKey} deriving (Eq, Show)
@@ -111,9 +111,6 @@ instance IsString FullPrivateKey where
instance IsString PublicKey where
fromString = parseString (decode >=> decodePubKey)
parseString :: (ByteString -> Either String a) -> (String -> a)
parseString parse = either error id . parse . B.pack
instance ToField SafePrivateKey where toField = toField . encodePrivKey
instance ToField PublicKey where toField = toField . encodePubKey
+3
View File
@@ -50,6 +50,9 @@ parseRead2 = parseRead $ do
w2 <- A.takeTill (== ' ')
pure $ w1 <> " " <> w2
parseString :: (ByteString -> Either String a) -> (String -> a)
parseString p = either error id . p . B.pack
blobFieldParser :: Typeable k => Parser k -> FieldParser k
blobFieldParser p = \case
f@(Field (SQLBlob b) _) ->
+12 -19
View File
@@ -13,7 +13,6 @@ import Control.Concurrent
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import SMPAgentClient
import SMPClient (testKeyHashStr)
import Simplex.Messaging.Agent.Transmission
import Simplex.Messaging.Protocol (ErrorType (..), MsgBody)
import System.IO (Handle)
@@ -26,12 +25,14 @@ agentTests = do
describe "SMP agent protocol syntax" syntaxTests
describe "Establishing duplex connection" do
it "should connect via one server and one agent" $
smpAgentTest2_1 testDuplexConnection
smpAgentTest2_1_1 testDuplexConnection
it "should connect via one server and 2 agents" $
smpAgentTest2 testDuplexConnection
smpAgentTest2_2_1 testDuplexConnection
it "should connect via 2 servers and 2 agents" $
smpAgentTest2_2_2 testDuplexConnection
describe "Connection subscriptions" do
xit "should connect via one server and one agent" $
smpAgentTest3_1 testSubscription
it "should connect via one server and one agent" $
smpAgentTest3_1_1 testSubscription
it "should send notifications to client when server disconnects" $
smpAgentServerTest testSubscrNotification
@@ -84,7 +85,7 @@ pattern Msg msgBody <- MSG {msgBody, msgIntegrity = MsgOk}
testDuplexConnection :: Handle -> Handle -> IO ()
testDuplexConnection alice bob = do
("1", "bob", Right (INV qInfo)) <- alice #: ("1", "bob", "NEW localhost:5000")
("1", "bob", Right (INV qInfo)) <- alice #: ("1", "bob", "NEW")
let qInfo' = serializeSmpQueueInfo qInfo
bob #: ("11", "alice", "JOIN " <> qInfo') #> ("11", "alice", CON)
alice <# ("", "bob", CON)
@@ -103,17 +104,14 @@ testDuplexConnection alice bob = do
testSubscription :: Handle -> Handle -> Handle -> IO ()
testSubscription alice1 alice2 bob = do
("1", "bob", Right (INV qInfo)) <- alice1 #: ("1", "bob", "NEW localhost:5000")
("1", "bob", Right (INV qInfo)) <- alice1 #: ("1", "bob", "NEW")
let qInfo' = serializeSmpQueueInfo qInfo
bob #: ("11", "alice", "JOIN " <> qInfo') #> ("11", "alice", CON)
bob #: ("12", "alice", "SEND 5\nhello") =#> \case ("12", "alice", SENT _) -> True; _ -> False
bob #: ("13", "alice", "SEND 11\nhello again") =#> \case ("13", "alice", SENT _) -> True; _ -> False
alice1 <# ("", "bob", CON)
alice1 <#= \case ("", "bob", Msg "hello") -> True; _ -> False
-- alice1 <#= \case ("", "bob", Msg "hello again") -> True; _ -> False
t <- tGet SAgent alice1
print t
t `shouldSatisfy` (\case ("", "bob", Msg "hello again") -> True; _ -> False) . correctTransmission
alice1 <#= \case ("", "bob", Msg "hello again") -> True; _ -> False
alice2 #: ("21", "bob", "SUB") #> ("21", "bob", OK)
alice1 <# ("", "bob", END)
bob #: ("14", "alice", "SEND 2\nhi") =#> \case ("14", "alice", SENT _) -> True; _ -> False
@@ -122,7 +120,7 @@ testSubscription alice1 alice2 bob = do
testSubscrNotification :: (ThreadId, ThreadId) -> Handle -> IO ()
testSubscrNotification (server, _) client = do
client #: ("1", "conn1", "NEW localhost:5000") =#> \case ("1", "conn1", INV _) -> True; _ -> False
client #: ("1", "conn1", "NEW") =#> \case ("1", "conn1", INV _) -> True; _ -> False
client #:# "nothing should be delivered to client before the server is killed"
killThread server
client <# ("", "conn1", END)
@@ -137,15 +135,10 @@ syntaxTests = do
describe "valid" do
-- TODO: ERROR no connection alias in the response (it does not generate it yet if not provided)
-- TODO: add tests with defined connection alias
xit "only server" $ ("211", "", "NEW localhost") >#>= \case ("211", "", "INV" : _) -> True; _ -> False
it "with port" $ ("212", "", "NEW localhost:5000") >#>= \case ("212", "", "INV" : _) -> True; _ -> False
xit "with keyHash" $ ("213", "", "NEW localhost#" <> testKeyHashStr) >#>= \case ("213", "", "INV" : _) -> True; _ -> False
it "with port and keyHash" $ ("214", "", "NEW localhost:5000#" <> testKeyHashStr) >#>= \case ("214", "", "INV" : _) -> True; _ -> False
xit "without parameters" $ ("211", "", "NEW") >#>= \case ("211", "", "INV" : _) -> True; _ -> False
describe "invalid" do
-- TODO: add tests with defined connection alias
it "no parameters" $ ("221", "", "NEW") >#> ("221", "", "ERR CMD SYNTAX")
it "many parameters" $ ("222", "", "NEW localhost:5000 hi") >#> ("222", "", "ERR CMD SYNTAX")
it "invalid server keyHash" $ ("223", "", "NEW localhost:5000#1") >#> ("223", "", "ERR CMD SYNTAX")
it "with parameters" $ ("222", "", "NEW hi") >#> ("222", "", "ERR CMD SYNTAX")
describe "JOIN" do
describe "valid" do
+55 -25
View File
@@ -8,8 +8,17 @@ module SMPAgentClient where
import Control.Monad.IO.Unlift
import Crypto.Random
import qualified Data.List.NonEmpty as L
import Network.Socket (HostName, ServiceName)
import SMPClient (serverBracket, testPort, withSmpServer, withSmpServerThreadOn)
import SMPClient
( serverBracket,
testKeyHash,
testPort,
testPort2,
withSmpServer,
withSmpServerOn,
withSmpServerThreadOn,
)
import Simplex.Messaging.Agent (runSMPAgentBlocking)
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Transmission
@@ -24,13 +33,13 @@ agentTestHost :: HostName
agentTestHost = "localhost"
agentTestPort :: ServiceName
agentTestPort = "5001"
agentTestPort = "5010"
agentTestPort2 :: ServiceName
agentTestPort2 = "5011"
agentTestPort3 :: ServiceName
agentTestPort3 = "5021"
agentTestPort3 = "5012"
testDB :: String
testDB = "tests/tmp/smp-agent.test.protocol.db"
@@ -50,18 +59,18 @@ runSmpAgentTest test = withSmpServer . withSmpAgent $ testSMPAgentClient test
runSmpAgentServerTest :: (MonadUnliftIO m, MonadRandom m) => ((ThreadId, ThreadId) -> Handle -> m a) -> m a
runSmpAgentServerTest test =
withSmpServerThreadOn testPort $
\server -> withSmpAgentThreadOn (agentTestPort, testDB) $
\server -> withSmpAgentThreadOn (agentTestPort, testPort, testDB) $
\agent -> testSMPAgentClient $ test (server, agent)
smpAgentServerTest :: ((ThreadId, ThreadId) -> Handle -> IO ()) -> Expectation
smpAgentServerTest test' = runSmpAgentServerTest test' `shouldReturn` ()
runSmpAgentTestN :: forall m a. (MonadUnliftIO m, MonadRandom m) => [(ServiceName, String)] -> ([Handle] -> m a) -> m a
runSmpAgentTestN :: forall m a. (MonadUnliftIO m, MonadRandom m) => [(ServiceName, ServiceName, String)] -> ([Handle] -> m a) -> m a
runSmpAgentTestN agents test = withSmpServer $ run agents []
where
run :: [(ServiceName, String)] -> [Handle] -> m a
run :: [(ServiceName, ServiceName, String)] -> [Handle] -> m a
run [] hs = test hs
run (a@(p, _) : as) hs = withSmpAgentOn a $ testSMPAgentClientOn p $ \h -> run as (h : hs)
run (a@(p, _, _) : as) hs = withSmpAgentOn a $ testSMPAgentClientOn p $ \h -> run as (h : hs)
runSmpAgentTestN_1 :: forall m a. (MonadUnliftIO m, MonadRandom m) => Int -> ([Handle] -> m a) -> m a
runSmpAgentTestN_1 nClients test = withSmpServer . withSmpAgent $ run nClients []
@@ -70,21 +79,37 @@ runSmpAgentTestN_1 nClients test = withSmpServer . withSmpAgent $ run nClients [
run 0 hs = test hs
run n hs = testSMPAgentClient $ \h -> run (n - 1) (h : hs)
smpAgentTestN :: [(ServiceName, String)] -> ([Handle] -> IO ()) -> Expectation
smpAgentTestN :: [(ServiceName, ServiceName, String)] -> ([Handle] -> IO ()) -> Expectation
smpAgentTestN agents test' = runSmpAgentTestN agents test' `shouldReturn` ()
smpAgentTestN_1 :: Int -> ([Handle] -> IO ()) -> Expectation
smpAgentTestN_1 n test' = runSmpAgentTestN_1 n test' `shouldReturn` ()
smpAgentTest2 :: (Handle -> Handle -> IO ()) -> Expectation
smpAgentTest2 test' =
smpAgentTestN [(agentTestPort, testDB), (agentTestPort2, testDB2)] _test
smpAgentTest2_2_2 :: (Handle -> Handle -> IO ()) -> Expectation
smpAgentTest2_2_2 test' =
withSmpServerOn testPort2 $
smpAgentTestN
[ (agentTestPort, testPort, testDB),
(agentTestPort2, testPort2, testDB2)
]
_test
where
_test [h1, h2] = test' h1 h2
_test _ = error "expected 2 handles"
smpAgentTest2_1 :: (Handle -> Handle -> IO ()) -> Expectation
smpAgentTest2_1 test' = smpAgentTestN_1 2 _test
smpAgentTest2_2_1 :: (Handle -> Handle -> IO ()) -> Expectation
smpAgentTest2_2_1 test' =
smpAgentTestN
[ (agentTestPort, testPort, testDB),
(agentTestPort2, testPort, testDB2)
]
_test
where
_test [h1, h2] = test' h1 h2
_test _ = error "expected 2 handles"
smpAgentTest2_1_1 :: (Handle -> Handle -> IO ()) -> Expectation
smpAgentTest2_1_1 test' = smpAgentTestN_1 2 _test
where
_test [h1, h2] = test' h1 h2
_test _ = error "expected 2 handles"
@@ -92,14 +117,17 @@ smpAgentTest2_1 test' = smpAgentTestN_1 2 _test
smpAgentTest3 :: (Handle -> Handle -> Handle -> IO ()) -> Expectation
smpAgentTest3 test' =
smpAgentTestN
[(agentTestPort, testDB), (agentTestPort2, testDB2), (agentTestPort3, testDB3)]
[ (agentTestPort, testPort, testDB),
(agentTestPort2, testPort, testDB2),
(agentTestPort3, testPort, testDB3)
]
_test
where
_test [h1, h2, h3] = test' h1 h2 h3
_test _ = error "expected 3 handles"
smpAgentTest3_1 :: (Handle -> Handle -> Handle -> IO ()) -> Expectation
smpAgentTest3_1 test' = smpAgentTestN_1 3 _test
smpAgentTest3_1_1 :: (Handle -> Handle -> Handle -> IO ()) -> Expectation
smpAgentTest3_1_1 test' = smpAgentTestN_1 3 _test
where
_test [h1, h2, h3] = test' h1 h2 h3
_test _ = error "expected 3 handles"
@@ -108,6 +136,7 @@ cfg :: AgentConfig
cfg =
AgentConfig
{ tcpPort = agentTestPort,
smpServers = L.fromList ["localhost:5000#KXNE1m2E1m0lm92WGKet9CL6+lO742Vy5G6nsrkvgs8="],
rsaKeySize = 2048 `div` 8,
connIdBytes = 12,
tbqSize = 1,
@@ -120,23 +149,24 @@ cfg =
}
}
withSmpAgentThreadOn :: (MonadUnliftIO m, MonadRandom m) => (ServiceName, String) -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn (port', db') =
serverBracket
(\started -> runSMPAgentBlocking started cfg {tcpPort = port', dbFile = db'})
(removeFile db')
withSmpAgentThreadOn :: (MonadUnliftIO m, MonadRandom m) => (ServiceName, ServiceName, String) -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn (port', smpPort', db') =
let cfg' = cfg {tcpPort = port', dbFile = db', smpServers = L.fromList [SMPServer "localhost" (Just smpPort') testKeyHash]}
in serverBracket
(`runSMPAgentBlocking` cfg')
(removeFile db')
withSmpAgentOn :: (MonadUnliftIO m, MonadRandom m) => (ServiceName, String) -> m a -> m a
withSmpAgentOn (port', db') = withSmpAgentThreadOn (port', db') . const
withSmpAgentOn :: (MonadUnliftIO m, MonadRandom m) => (ServiceName, ServiceName, String) -> m a -> m a
withSmpAgentOn (port', smpPort', db') = withSmpAgentThreadOn (port', smpPort', db') . const
withSmpAgent :: (MonadUnliftIO m, MonadRandom m) => m a -> m a
withSmpAgent = withSmpAgentOn (agentTestPort, testDB)
withSmpAgent = withSmpAgentOn (agentTestPort, testPort, testDB)
testSMPAgentClientOn :: MonadUnliftIO m => ServiceName -> (Handle -> m a) -> m a
testSMPAgentClientOn port' client = do
runTCPClient agentTestHost port' $ \h -> do
line <- liftIO $ getLn h
if line == "Welcome to SMP v0.2.0 agent"
if line == "Welcome to SMP v0.3.0 agent"
then client h
else error "not connected"
+3
View File
@@ -32,6 +32,9 @@ testHost = "localhost"
testPort :: ServiceName
testPort = "5000"
testPort2 :: ServiceName
testPort2 = "5001"
testKeyHashStr :: B.ByteString
testKeyHashStr = "KXNE1m2E1m0lm92WGKet9CL6+lO742Vy5G6nsrkvgs8="