Compare commits

..
Author SHA1 Message Date
John Roberts 8c298728e2 wip 2022-03-12 22:57:07 +04:00
Efim Poberezkin cca8ac5a58 init, debugging (some data is being written to db) 2022-02-04 13:59:14 +04:00
Efim Poberezkin b1d2d45947 compiles 2022-02-04 12:45:05 +04:00
Efim Poberezkin c9c6d2b2d3 some instances 2022-02-03 17:57:09 +04:00
Efim Poberezkin 85c09d1703 re-trigger build 2022-02-03 17:43:01 +04:00
Efim Poberezkin 08b43b42a0 test compilation 2022-02-03 17:20:49 +04:00
Efim Poberezkin 4980db932d use posgres fork 2022-02-03 15:06:25 +04:00
18 changed files with 745 additions and 465 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ module Main where
import Control.Logger.Simple
import qualified Data.List.NonEmpty as L
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Env.Postgres
import Simplex.Messaging.Agent.Server (runSMPAgent)
import Simplex.Messaging.Transport (TLS, Transport (..))
+1
View File
@@ -47,6 +47,7 @@ dependencies:
- mtl == 2.2.*
- network == 3.1.*
- network-transport == 0.5.*
- postgresql-simple == 0.6.*
- QuickCheck == 2.14.*
- random >= 1.1 && < 1.3
- simple-logger == 0.1.*
+4
View File
@@ -96,6 +96,7 @@ library
, mtl ==2.2.*
, network ==3.1.*
, network-transport ==0.5.*
, postgresql-simple ==0.6.*
, random >=1.1 && <1.3
, simple-logger ==0.1.*
, sqlite-simple ==0.4.*
@@ -147,6 +148,7 @@ executable smp-agent
, mtl ==2.2.*
, network ==3.1.*
, network-transport ==0.5.*
, postgresql-simple ==0.6.*
, random >=1.1 && <1.3
, simple-logger ==0.1.*
, simplexmq
@@ -201,6 +203,7 @@ executable smp-server
, network ==3.1.*
, network-transport ==0.5.*
, optparse-applicative >=0.15 && <0.17
, postgresql-simple ==0.6.*
, process ==1.6.*
, random >=1.1 && <1.3
, simple-logger ==0.1.*
@@ -269,6 +272,7 @@ test-suite smp-server-test
, mtl ==2.2.*
, network ==3.1.*
, network-transport ==0.5.*
, postgresql-simple ==0.6.*
, random >=1.1 && <1.3
, simple-logger ==0.1.*
, simplexmq
+16 -6
View File
@@ -70,11 +70,11 @@ import Data.Time.Clock
import Data.Time.Clock.System (systemToUTCTime)
import Database.SQLite.Simple (SQLError)
import Simplex.Messaging.Agent.Client
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Env.Postgres
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Agent.Store
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore)
import Simplex.Messaging.Agent.Store.Postgres (PostgresStore)
import Simplex.Messaging.Client (SMPServerTransmission)
import qualified Simplex.Messaging.Crypto as C
import qualified Simplex.Messaging.Crypto.Ratchet as CR
@@ -172,18 +172,22 @@ client c@AgentClient {rcvQ, subQ} = forever $ do
withStore ::
AgentMonad m =>
(forall m'. (MonadUnliftIO m', MonadError StoreError m') => SQLiteStore -> m' a) ->
(forall m'. (MonadUnliftIO m', MonadError StoreError m') => PostgresStore -> m' a) ->
m a
withStore action = do
st <- asks store
runExceptT (action st `E.catch` handleInternal) >>= \case
Right c -> return c
Left e -> throwError $ storeError e
Left e -> do
liftIO $ print e
throwError $ storeError e
where
-- TODO when parsing exception happens in store, the agent hangs;
-- changing SQLError to SomeException does not help
handleInternal :: (MonadError StoreError m') => SQLError -> m' a
handleInternal e = throwError . SEInternal $ bshow e
handleInternal :: (MonadUnliftIO m', MonadError StoreError m') => SQLError -> m' a
handleInternal e = do
liftIO $ print e
throwError . SEInternal $ bshow e
storeError :: StoreError -> AgentErrorType
storeError = \case
SEConnNotFound -> CONN NOT_FOUND
@@ -238,8 +242,11 @@ joinConn c connId (CRInvitationUri (ConnReqUriData _ agentVRange (qUri :| _)) e2
g <- asks idsDrg
let cData = ConnData {connId}
connId' <- withStore $ \st -> do
liftIO $ print "before: createSndConn st g cData sq"
connId' <- createSndConn st g cData sq
liftIO $ print "before: createRatchet st connId' rc"
createRatchet st connId' rc
liftIO $ print "after: createRatchet st connId' rc"
pure connId'
confirmQueue c connId' sq smpConf $ Just e2eSndParams
void $ enqueueMessage c connId' sq HELLO
@@ -621,9 +628,12 @@ processSMPTransmission c@AgentClient {subQ} (srv, rId, cmd) = do
Nothing -> notify . ERR $ AGENT A_VERSION
Just qInfo' -> do
(sq, smpConf) <- newSndQueue qInfo' ownConnInfo
liftIO $ print "before: upgradeRcvConnToDuplex st connId sq"
withStore $ \st -> upgradeRcvConnToDuplex st connId sq
confirmQueue c connId sq smpConf Nothing
liftIO $ print "before: `removeConfirmations` connId"
withStore (`removeConfirmations` connId)
liftIO $ print "after: `removeConfirmations` connId"
void $ enqueueMessage c connId sq HELLO
_ -> prohibited
+1 -1
View File
@@ -53,7 +53,7 @@ import Data.Maybe (isNothing)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Text.Encoding
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Env.Postgres
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Agent.Store
+8 -7
View File
@@ -16,12 +16,13 @@ import Control.Monad.IO.Unlift
import Crypto.Random
import Data.List.NonEmpty (NonEmpty)
import Data.Time.Clock (NominalDiffTime, nominalDay)
import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo)
import Network.Socket
import Numeric.Natural
import Simplex.Messaging.Agent.Protocol (SMPServer)
import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Agent.Store.SQLite
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
import Simplex.Messaging.Agent.Store.Postgres
import qualified Simplex.Messaging.Agent.Store.Postgres.Migrations as Migrations
import Simplex.Messaging.Client
import qualified Simplex.Messaging.Crypto as C
import System.Random (StdGen, newStdGen)
@@ -33,7 +34,7 @@ data AgentConfig = AgentConfig
cmdSignAlg :: C.SignAlg,
connIdBytes :: Int,
tbqSize :: Natural,
dbFile :: FilePath,
dbConnInfo :: ConnectInfo,
dbPoolSize :: Int,
smpCfg :: SMPClientConfig,
reconnectInterval :: RetryInterval,
@@ -51,7 +52,7 @@ defaultAgentConfig =
cmdSignAlg = C.SignAlg C.SEd448,
connIdBytes = 12,
tbqSize = 16,
dbFile = "smp-agent.db",
dbConnInfo = defaultConnectInfo {connectDatabase = "agent_poc_1"},
dbPoolSize = 4,
smpCfg = smpDefaultConfig,
reconnectInterval =
@@ -72,16 +73,16 @@ defaultAgentConfig =
data Env = Env
{ config :: AgentConfig,
store :: SQLiteStore,
store :: PostgresStore,
idsDrg :: TVar ChaChaDRG,
clientCounter :: TVar Int,
randomServer :: TVar StdGen
}
newSMPAgentEnv :: (MonadUnliftIO m, MonadRandom m) => AgentConfig -> m Env
newSMPAgentEnv cfg = do
newSMPAgentEnv cfg@AgentConfig {dbConnInfo, dbPoolSize} = do
idsDrg <- newTVarIO =<< drgNew
store <- liftIO $ createSQLiteStore (dbFile cfg) (dbPoolSize cfg) Migrations.app
store <- liftIO $ createPostgresStore dbConnInfo dbPoolSize Migrations.app
clientCounter <- newTVarIO 0
randomServer <- newTVarIO =<< liftIO newStdGen
return Env {config = cfg, store, idsDrg, clientCounter, randomServer}
+1 -1
View File
@@ -19,7 +19,7 @@ import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Text.Encoding (decodeUtf8)
import Simplex.Messaging.Agent
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Env.Postgres
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Transport (ATransport (..), TProxy, Transport (..), simplexMQVersion)
import Simplex.Messaging.Transport.Server (loadTLSServerParams, runTransportServer)
File diff suppressed because it is too large Load Diff
@@ -15,18 +15,19 @@ module Simplex.Messaging.Agent.Store.Postgres.Migrations
)
where
import Control.Monad (forM_)
import Control.Monad (forM_, void)
import Data.Function (on)
import Data.List (intercalate, sortBy)
import Data.Text (Text)
import Data.Time.Clock (getCurrentTime)
import Database.SQLite.Simple (Connection, Only (..), Query (..))
import qualified Database.SQLite.Simple as DB
import Database.SQLite.Simple.QQ (sql)
import qualified Database.SQLite3 as SQLite3
import Simplex.Messaging.Agent.Store.Postgres.Migrations.M20220202_initial
import Database.PostgreSQL.Simple (Connection, Only (..))
import qualified Database.PostgreSQL.Simple as DB
import Database.PostgreSQL.Simple.Internal (exec)
import Database.PostgreSQL.Simple.SqlQQ (sql)
import Database.PostgreSQL.Simple.Transaction (withTransaction)
import Database.PostgreSQL.Simple.Types (Query (..))
import Simplex.Messaging.Agent.Store.Postgres.Migrations.M20220202_initial (m20220202_initial)
data Migration = Migration {name :: String, up :: Text}
data Migration = Migration {name :: String, up :: Query}
deriving (Show)
schemaMigrations :: [(String, Query)]
@@ -38,7 +39,7 @@ schemaMigrations =
app :: [Migration]
app = sortBy (compare `on` name) $ map migration schemaMigrations
where
migration (name, query) = Migration {name = name, up = fromQuery query}
migration (name, query) = Migration {name, up = query}
get :: Connection -> [Migration] -> IO (Either String [Migration])
get conn migrations =
@@ -46,23 +47,23 @@ get conn migrations =
<$> DB.query_ conn "SELECT name FROM migrations ORDER BY name ASC;"
run :: Connection -> [Migration] -> IO ()
run conn ms = DB.withImmediateTransaction conn . forM_ ms $
\Migration {name, up} -> insert name >> execSQL up
run conn ms = withTransaction conn . forM_ ms $
\Migration {name, up} -> insert name >> exec conn (fromQuery up)
where
insert name = DB.execute conn "INSERT INTO migrations (name, ts) VALUES (?, ?);" . (name,) =<< getCurrentTime
execSQL = SQLite3.exec $ DB.connectionHandle conn
initialize :: Connection -> IO ()
initialize conn =
DB.execute_
conn
[sql|
CREATE TABLE IF NOT EXISTS migrations (
name TEXT NOT NULL,
ts TEXT NOT NULL,
PRIMARY KEY (name)
);
|]
void $
DB.execute_
conn
[sql|
CREATE TABLE IF NOT EXISTS migrations (
name TEXT NOT NULL,
ts TEXT NOT NULL,
PRIMARY KEY (name)
);
|]
migrationsToRun :: [Migration] -> [String] -> Either String [Migration]
migrationsToRun appMs [] = Right appMs
@@ -2,42 +2,46 @@
module Simplex.Messaging.Agent.Store.Postgres.Migrations.M20220202_initial where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
import Database.PostgreSQL.Simple (Query)
import Database.PostgreSQL.Simple.SqlQQ (sql)
m20220202_initial :: Query
m20220202_initial =
[sql|
-- for easy testing
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
CREATE TABLE servers (
host TEXT NOT NULL,
port TEXT NOT NULL,
key_hash BLOB NOT NULL,
key_hash BYTEA NOT NULL,
PRIMARY KEY (host, port)
) WITHOUT ROWID;
);
CREATE TABLE connections (
conn_id BLOB NOT NULL PRIMARY KEY,
conn_id BYTEA NOT NULL PRIMARY KEY,
conn_mode TEXT NOT NULL,
last_internal_msg_id INTEGER NOT NULL DEFAULT 0,
last_internal_rcv_msg_id INTEGER NOT NULL DEFAULT 0,
last_internal_snd_msg_id INTEGER NOT NULL DEFAULT 0,
last_external_snd_msg_id INTEGER NOT NULL DEFAULT 0,
last_rcv_msg_hash BLOB NOT NULL DEFAULT x'',
last_snd_msg_hash BLOB NOT NULL DEFAULT x'',
last_rcv_msg_hash BYTEA NOT NULL DEFAULT '',
last_snd_msg_hash BYTEA NOT NULL DEFAULT '',
smp_agent_version INTEGER NOT NULL DEFAULT 1
) WITHOUT ROWID;
);
CREATE TABLE rcv_queues (
host TEXT NOT NULL,
port TEXT NOT NULL,
rcv_id BLOB NOT NULL,
conn_id BLOB NOT NULL REFERENCES connections ON DELETE CASCADE,
rcv_private_key BLOB NOT NULL,
rcv_dh_secret BLOB NOT NULL,
e2e_priv_key BLOB NOT NULL,
e2e_dh_secret BLOB,
snd_id BLOB NOT NULL,
snd_key BLOB,
rcv_id BYTEA NOT NULL,
conn_id BYTEA NOT NULL REFERENCES connections ON DELETE CASCADE,
rcv_private_key BYTEA NOT NULL,
rcv_dh_secret BYTEA NOT NULL,
e2e_priv_key BYTEA NOT NULL,
e2e_dh_secret BYTEA,
snd_id BYTEA NOT NULL,
snd_key BYTEA,
status TEXT NOT NULL,
smp_server_version INTEGER NOT NULL DEFAULT 1,
smp_client_version INTEGER,
@@ -45,104 +49,110 @@ CREATE TABLE rcv_queues (
FOREIGN KEY (host, port) REFERENCES servers
ON DELETE RESTRICT ON UPDATE CASCADE,
UNIQUE (host, port, snd_id)
) WITHOUT ROWID;
);
CREATE TABLE snd_queues (
host TEXT NOT NULL,
port TEXT NOT NULL,
snd_id BLOB NOT NULL,
conn_id BLOB NOT NULL REFERENCES connections ON DELETE CASCADE,
snd_private_key BLOB NOT NULL,
e2e_dh_secret BLOB NOT NULL,
snd_id BYTEA NOT NULL,
conn_id BYTEA NOT NULL REFERENCES connections ON DELETE CASCADE,
snd_private_key BYTEA NOT NULL,
e2e_dh_secret BYTEA NOT NULL,
status TEXT NOT NULL,
smp_server_version INTEGER NOT NULL DEFAULT 1,
smp_client_version INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (host, port, snd_id),
FOREIGN KEY (host, port) REFERENCES servers
ON DELETE RESTRICT ON UPDATE CASCADE
) WITHOUT ROWID;
);
CREATE TABLE messages (
conn_id BLOB NOT NULL REFERENCES connections (conn_id)
conn_id BYTEA NOT NULL REFERENCES connections (conn_id)
ON DELETE CASCADE,
internal_id INTEGER NOT NULL,
internal_ts TEXT NOT NULL,
internal_ts TIMESTAMP NOT NULL,
internal_rcv_id INTEGER,
internal_snd_id INTEGER,
msg_type BLOB NOT NULL, -- (H)ELLO, (R)EPLY, (D)ELETE. Should SMP confirmation be saved too?
msg_body BLOB NOT NULL DEFAULT x'',
PRIMARY KEY (conn_id, internal_id),
FOREIGN KEY (conn_id, internal_rcv_id) REFERENCES rcv_messages
ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY (conn_id, internal_snd_id) REFERENCES snd_messages
ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
) WITHOUT ROWID;
msg_type BYTEA NOT NULL, -- (H)ELLO, (R)EPLY, (D)ELETE. Should SMP confirmation be saved too?
msg_body BYTEA NOT NULL DEFAULT '',
PRIMARY KEY (conn_id, internal_id)
);
CREATE TABLE rcv_messages (
conn_id BLOB NOT NULL,
conn_id BYTEA NOT NULL,
internal_rcv_id INTEGER NOT NULL,
internal_id INTEGER NOT NULL,
external_snd_id INTEGER NOT NULL,
broker_id BLOB NOT NULL,
broker_ts TEXT NOT NULL,
internal_hash BLOB NOT NULL,
external_prev_snd_hash BLOB NOT NULL,
integrity BLOB NOT NULL,
broker_id BYTEA NOT NULL,
broker_ts TIMESTAMP NOT NULL,
internal_hash BYTEA NOT NULL,
external_prev_snd_hash BYTEA NOT NULL,
integrity BYTEA NOT NULL, -- in the list of keywords
PRIMARY KEY (conn_id, internal_rcv_id),
FOREIGN KEY (conn_id, internal_id) REFERENCES messages
ON DELETE CASCADE
) WITHOUT ROWID;
);
ALTER TABLE messages
ADD CONSTRAINT fk_messages_rcv_messages
FOREIGN KEY (conn_id, internal_rcv_id) REFERENCES rcv_messages
ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
CREATE TABLE snd_messages (
conn_id BLOB NOT NULL,
conn_id BYTEA NOT NULL,
internal_snd_id INTEGER NOT NULL,
internal_id INTEGER NOT NULL,
internal_hash BLOB NOT NULL,
previous_msg_hash BLOB NOT NULL DEFAULT x'',
internal_hash BYTEA NOT NULL,
previous_msg_hash BYTEA NOT NULL DEFAULT '',
PRIMARY KEY (conn_id, internal_snd_id),
FOREIGN KEY (conn_id, internal_id) REFERENCES messages
ON DELETE CASCADE
) WITHOUT ROWID;
);
ALTER TABLE messages
ADD CONSTRAINT fk_messages_snd_messages
FOREIGN KEY (conn_id, internal_snd_id) REFERENCES snd_messages
ON DELETE CASCADE DEFERRABLE INITIALLY deferred;
CREATE TABLE conn_confirmations (
confirmation_id BLOB NOT NULL PRIMARY KEY,
conn_id BLOB NOT NULL REFERENCES connections ON DELETE CASCADE,
e2e_snd_pub_key BLOB NOT NULL, -- TODO per-queue key. Split?
sender_key BLOB NOT NULL, -- TODO per-queue key. Split?
ratchet_state BLOB NOT NULL,
sender_conn_info BLOB NOT NULL,
confirmation_id BYTEA NOT NULL PRIMARY KEY,
conn_id BYTEA NOT NULL REFERENCES connections ON DELETE CASCADE,
e2e_snd_pub_key BYTEA NOT NULL, -- TODO per-queue key. Split?
sender_key BYTEA NOT NULL, -- TODO per-queue key. Split?
ratchet_state BYTEA NOT NULL,
sender_conn_info BYTEA NOT NULL,
accepted INTEGER NOT NULL,
own_conn_info BLOB,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
) WITHOUT ROWID;
own_conn_info BYTEA,
created_at TIMESTAMP NOT NULL DEFAULT (now())
);
CREATE TABLE conn_invitations (
invitation_id BLOB NOT NULL PRIMARY KEY,
contact_conn_id BLOB NOT NULL REFERENCES connections ON DELETE CASCADE,
cr_invitation BLOB NOT NULL,
recipient_conn_info BLOB NOT NULL,
invitation_id BYTEA NOT NULL PRIMARY KEY,
contact_conn_id BYTEA NOT NULL REFERENCES connections ON DELETE CASCADE,
cr_invitation BYTEA NOT NULL,
recipient_conn_info BYTEA NOT NULL,
accepted INTEGER NOT NULL DEFAULT 0,
own_conn_info BLOB,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
) WITHOUT ROWID;
own_conn_info BYTEA,
created_at TIMESTAMP NOT NULL DEFAULT (now())
);
CREATE TABLE ratchets (
conn_id BLOB NOT NULL PRIMARY KEY REFERENCES connections
conn_id BYTEA NOT NULL PRIMARY KEY REFERENCES connections
ON DELETE CASCADE,
-- x3dh keys are not saved on the sending side (the side accepting the connection)
x3dh_priv_key_1 BLOB,
x3dh_priv_key_2 BLOB,
x3dh_priv_key_1 BYTEA,
x3dh_priv_key_2 BYTEA,
-- ratchet is initially empty on the receiving side (the side offering the connection)
ratchet_state BLOB,
ratchet_state BYTEA,
e2e_version INTEGER NOT NULL DEFAULT 1
) WITHOUT ROWID;
);
CREATE TABLE skipped_messages (
skipped_message_id INTEGER PRIMARY KEY,
conn_id BLOB NOT NULL REFERENCES ratchets
skipped_message_id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
conn_id BYTEA NOT NULL REFERENCES ratchets
ON DELETE CASCADE,
header_key BLOB NOT NULL,
header_key BYTEA NOT NULL,
msg_n INTEGER NOT NULL,
msg_key BLOB NOT NULL
msg_key BYTEA NOT NULL
);
|]
@@ -0,0 +1,9 @@
# Postgres setup
Create three databases - `agent_poc_1`, `agent_poc_2`, `agent_poc_3` - and have Postgres server running.
~~`brew install postgresql` - required by postgresql-simple.~~
~~You may run into compilation errors, then you might also need to `brew install libpq --build-from-source`, see [this Stack Overflow answer](https://stackoverflow.com/a/70012033).~~
In the end I managed to build using cabal.
+77 -20
View File
@@ -149,14 +149,20 @@ import Data.String
import Data.Type.Equality
import Data.Typeable (Typeable)
import Data.X509
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import qualified Database.PostgreSQL.Simple as PDB
import qualified Database.PostgreSQL.Simple.FromField as PF
import qualified Database.PostgreSQL.Simple.ToField as PT
import qualified Database.PostgreSQL.Simple.TypeInfo as PTI
import qualified Database.PostgreSQL.Simple.TypeInfo.Static as PTIS
import qualified Database.SQLite.Simple.FromField as SF
import qualified Database.SQLite.Simple.ToField as ST
import GHC.TypeLits (ErrorMessage (..), TypeError)
import Network.Transport.Internal (decodeWord16, encodeWord16)
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (blobFieldDecoder, parseAll, parseString)
import Simplex.Messaging.Util ((<$?>))
import qualified Database.PostgreSQL.Simple as PDB
-- | Cryptographic algorithms.
data Algorithm = Ed25519 | Ed448 | X25519 | X448
@@ -540,33 +546,62 @@ generateKeyPair' = case sAlgorithm @a of
let k = X448.toPublic pk
in pure (PublicKeyX448 k, PrivateKeyX448 pk k)
instance ToField APrivateSignKey where toField = toField . encodePrivKey
instance ST.ToField APrivateSignKey where toField = ST.toField . encodePrivKey
instance ToField APublicVerifyKey where toField = toField . encodePubKey
instance ST.ToField APublicVerifyKey where toField = ST.toField . encodePubKey
instance ToField APrivateDhKey where toField = toField . encodePrivKey
instance ST.ToField APrivateDhKey where toField = ST.toField . encodePrivKey
instance ToField APublicDhKey where toField = toField . encodePubKey
instance ST.ToField APublicDhKey where toField = ST.toField . encodePubKey
instance AlgorithmI a => ToField (PrivateKey a) where toField = toField . encodePrivKey
instance AlgorithmI a => ST.ToField (PrivateKey a) where toField = ST.toField . encodePrivKey
instance AlgorithmI a => ToField (PublicKey a) where toField = toField . encodePubKey
instance AlgorithmI a => ST.ToField (PublicKey a) where toField = ST.toField . encodePubKey
instance ToField (DhSecret a) where toField = toField . dhBytes'
instance ST.ToField (DhSecret a) where toField = ST.toField . dhBytes'
instance FromField APrivateSignKey where fromField = blobFieldDecoder decodePrivKey
instance SF.FromField APrivateSignKey where fromField = blobFieldDecoder decodePrivKey
instance FromField APublicVerifyKey where fromField = blobFieldDecoder decodePubKey
instance SF.FromField APublicVerifyKey where fromField = blobFieldDecoder decodePubKey
instance FromField APrivateDhKey where fromField = blobFieldDecoder decodePrivKey
instance SF.FromField APrivateDhKey where fromField = blobFieldDecoder decodePrivKey
instance FromField APublicDhKey where fromField = blobFieldDecoder decodePubKey
instance SF.FromField APublicDhKey where fromField = blobFieldDecoder decodePubKey
instance (Typeable a, AlgorithmI a) => FromField (PrivateKey a) where fromField = blobFieldDecoder decodePrivKey
instance (Typeable a, AlgorithmI a) => SF.FromField (PrivateKey a) where fromField = blobFieldDecoder decodePrivKey
instance (Typeable a, AlgorithmI a) => FromField (PublicKey a) where fromField = blobFieldDecoder decodePubKey
instance (Typeable a, AlgorithmI a) => SF.FromField (PublicKey a) where fromField = blobFieldDecoder decodePubKey
instance (Typeable a, AlgorithmI a) => FromField (DhSecret a) where fromField = blobFieldDecoder strDecode
instance (Typeable a, AlgorithmI a) => SF.FromField (DhSecret a) where fromField = blobFieldDecoder strDecode
instance PT.ToField APrivateSignKey where toField = PT.toField . encodePrivKey
instance PT.ToField APublicVerifyKey where toField = PT.toField . encodePubKey
instance PT.ToField APrivateDhKey where toField = PT.toField . encodePrivKey
instance PT.ToField APublicDhKey where toField = PT.toField . encodePubKey
instance AlgorithmI a => PT.ToField (PrivateKey a) where toField = PT.toField . encodePrivKey
instance AlgorithmI a => PT.ToField (PublicKey a) where toField = PT.toField . encodePubKey
instance PT.ToField (DhSecret a) where toField = PT.toField . PDB.Binary . dhBytes'
instance PF.FromField APrivateSignKey where fromField = fromByteStringField decodePrivKey
instance PF.FromField APublicVerifyKey where fromField = fromByteStringField decodePubKey
instance PF.FromField APrivateDhKey where fromField = fromByteStringField decodePrivKey
instance PF.FromField APublicDhKey where fromField = fromByteStringField decodePubKey
instance (Typeable a, AlgorithmI a) => PF.FromField (PrivateKey a) where fromField = fromByteStringField decodePrivKey
instance (Typeable a, AlgorithmI a) => PF.FromField (PublicKey a) where fromField = fromByteStringField decodePubKey
-- instance (Typeable a, AlgorithmI a) => PF.FromField (DhSecret a) where fromField = fromByteStringField strDecode
instance (Typeable a, AlgorithmI a) => PF.FromField (DhSecret a) where fromField x = fromByteStringField strDecode x
instance IsString (Maybe ASignature) where
fromString = parseString $ decode >=> decodeSignature
@@ -690,9 +725,13 @@ validSignatureSize n =
newtype Key = Key {unKey :: ByteString}
deriving (Eq, Ord, Show)
instance ToField Key where toField = toField . unKey
instance ST.ToField Key where toField = ST.toField . unKey
instance FromField Key where fromField f = Key <$> fromField f
instance PT.ToField Key where toField = PT.toField . unKey
instance SF.FromField Key where fromField f = Key <$> SF.fromField f
instance PF.FromField Key where fromField f = PF.fromField f
instance ToJSON Key where
toJSON = strToJSON . unKey
@@ -730,9 +769,27 @@ instance StrEncoding KeyHash where
instance IsString KeyHash where
fromString = parseString $ parseAll strP
instance ToField KeyHash where toField = toField . strEncode
instance ST.ToField KeyHash where toField = ST.toField . strEncode
instance FromField KeyHash where fromField = blobFieldDecoder $ parseAll strP
instance SF.FromField KeyHash where fromField = blobFieldDecoder $ parseAll strP
instance PT.ToField KeyHash where toField = PT.toField . strEncode
-- TODO
-- instance PF.FromField KeyHash where fromField = blobFieldDecoderPostgres $ parseAll strP
instance PF.FromField KeyHash where fromField = fromByteStringField $ parseAll strP
fromByteStringField :: Typeable a => (ByteString -> Either String a) -> PF.Field -> Maybe ByteString -> PF.Conversion a
fromByteStringField dec f mdata =
if PF.typeOid f /= PTI.typoid PTIS.bytea
then PF.returnError PF.Incompatible f ""
else case mdata of
Nothing -> PF.returnError PF.UnexpectedNull f ""
Just dat ->
case dec dat of
Right x -> return x
_ -> PF.returnError PF.ConversionFailed f (B.unpack dat)
-- | SHA256 digest.
sha256Hash :: ByteString -> ByteString
+29 -6
View File
@@ -30,8 +30,12 @@ import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import Data.Typeable (Typeable)
import Data.Word (Word32)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import qualified Database.PostgreSQL.Simple.FromField as PF
import qualified Database.PostgreSQL.Simple.ToField as PT
import qualified Database.PostgreSQL.Simple.TypeInfo as PTI
import qualified Database.PostgreSQL.Simple.TypeInfo.Static as PTIS
import qualified Database.SQLite.Simple.FromField as SF
import qualified Database.SQLite.Simple.ToField as ST
import GHC.Generics
import Simplex.Messaging.Agent.QueryString
import Simplex.Messaging.Crypto
@@ -197,13 +201,32 @@ instance ToJSON RatchetKey where
instance FromJSON RatchetKey where
parseJSON = fmap RatchetKey . strParseJSON "Key"
instance AlgorithmI a => ToField (Ratchet a) where toField = toField . LB.toStrict . J.encode
instance AlgorithmI a => ST.ToField (Ratchet a) where toField = ST.toField . LB.toStrict . J.encode
instance (AlgorithmI a, Typeable a) => FromField (Ratchet a) where fromField = blobFieldDecoder J.eitherDecodeStrict'
instance AlgorithmI a => PT.ToField (Ratchet a) where toField = PT.toField . LB.toStrict . J.encode
instance ToField MessageKey where toField = toField . smpEncode
instance (AlgorithmI a, Typeable a) => PF.FromField (Ratchet a) where fromField = fromByteStringField J.eitherDecodeStrict'
instance FromField MessageKey where fromField = blobFieldDecoder smpDecode
instance (AlgorithmI a, Typeable a) => SF.FromField (Ratchet a) where fromField = blobFieldDecoder J.eitherDecodeStrict'
instance ST.ToField MessageKey where toField = ST.toField . smpEncode
instance PT.ToField MessageKey where toField = PT.toField . smpEncode
instance SF.FromField MessageKey where fromField = blobFieldDecoder smpDecode
instance PF.FromField MessageKey where fromField = fromByteStringField smpDecode
fromByteStringField :: Typeable a => (ByteString -> Either String a) -> PF.Field -> Maybe ByteString -> PF.Conversion a
fromByteStringField dec f mdata =
if PF.typeOid f /= PTI.typoid PTIS.bytea
then PF.returnError PF.Incompatible f ""
else case mdata of
Nothing -> PF.returnError PF.UnexpectedNull f ""
Just dat ->
case dec dat of
Right x -> return x
_ -> PF.returnError PF.ConversionFailed f (B.unpack dat)
-- | Sending ratchet initialization, equivalent to RatchetInitAliceHE in double ratchet spec
--
+20 -9
View File
@@ -15,10 +15,13 @@ import Data.Char (isAlphaNum, toLower)
import Data.Time.Clock (UTCTime)
import Data.Time.ISO8601 (parseISO8601)
import Data.Typeable (Typeable)
import qualified Database.PostgreSQL.Simple.FromField as PF
import qualified Database.PostgreSQL.Simple.Internal as PI
import qualified Database.PostgreSQL.Simple.Ok as PO
import Database.SQLite.Simple (ResultError (..), SQLData (..))
import Database.SQLite.Simple.FromField (FieldParser, returnError)
import Database.SQLite.Simple.Internal (Field (..))
import Database.SQLite.Simple.Ok (Ok (Ok))
import qualified Database.SQLite.Simple.FromField as SF
import qualified Database.SQLite.Simple.Internal as SI
import qualified Database.SQLite.Simple.Ok as SO
import Simplex.Messaging.Util ((<$?>))
import Text.Read (readMaybe)
@@ -69,16 +72,24 @@ wordEnd c = c == ' ' || c == '\n'
parseString :: (ByteString -> Either String a) -> (String -> a)
parseString p = either error id . p . B.pack
blobFieldParser :: Typeable k => Parser k -> FieldParser k
blobFieldParser :: Typeable k => Parser k -> SF.FieldParser k
blobFieldParser = blobFieldDecoder . parseAll
blobFieldDecoder :: Typeable k => (ByteString -> Either String k) -> FieldParser k
blobFieldDecoder :: Typeable k => (ByteString -> Either String k) -> SF.FieldParser k
blobFieldDecoder dec = \case
f@(Field (SQLBlob b) _) ->
f@(SI.Field (SQLBlob b) _) ->
case dec b of
Right k -> Ok k
Left e -> returnError ConversionFailed f ("couldn't parse field: " ++ e)
f -> returnError ConversionFailed f "expecting SQLBlob column type"
Right k -> SO.Ok k
Left e -> SF.returnError SF.ConversionFailed f ("couldn't parse field: " ++ e)
f -> SF.returnError SF.ConversionFailed f "expecting SQLBlob column type"
-- blobFieldDecoderPostgres :: Typeable k => (ByteString -> Either String k) -> PF.FieldParser k
-- blobFieldDecoderPostgres dec = \case
-- f@(PI.Field b _ _) ->
-- case dec b of
-- Right k -> PO.Ok k
-- Left e -> PF.returnError PF.ConversionFailed f ("couldn't parse field: " ++ e)
-- f -> PF.returnError PF.ConversionFailed f "expecting SQLBlob column type"
fstToLower :: String -> String
fstToLower "" = ""
+4 -1
View File
@@ -13,6 +13,7 @@ import AgentTests.ConnectionRequestTests
import AgentTests.DoubleRatchetTests (doubleRatchetTests)
import AgentTests.FunctionalAPITests (functionalAPITests)
import AgentTests.SQLiteTests (storeTests)
import AgentTests.PostgresTests (postgresStoreTests)
import Control.Concurrent
import Control.Monad (forM_)
import Data.ByteString.Char8 (ByteString)
@@ -36,6 +37,7 @@ agentTests (ATransport t) = do
describe "Double ratchet tests" doubleRatchetTests
describe "Functional API" $ functionalAPITests (ATransport t)
describe "SQLite store" storeTests
describe "Postgres store" postgresStoreTests
describe "SMP agent protocol syntax" $ syntaxTests t
describe "Establishing duplex connection" $ do
it "should connect via one server and one agent" $
@@ -329,7 +331,7 @@ testMsgDeliveryAgentRestart t bob = do
bob #: ("12", "alice", "ACK 5") #> ("12", "alice", OK)
removeFile testStoreLogFile
removeFile testDB
-- removeFile testDB
where
withServer test' = withSmpServerStoreLogOn (ATransport t) testPort2 (const test') `shouldReturn` ()
withAgent = withSmpAgentThreadOn_ (ATransport t) (agentTestPort, testPort, testDB) (pure ()) . const . testSMPAgentClientOn agentTestPort
@@ -422,6 +424,7 @@ syntaxTests t = do
-- TODO: add tests with defined connection id
it "with incorrect parameter" $ ("222", "", "NEW hi") >#> ("222", "", "ERR CMD SYNTAX")
-- focus this test to test postgres
describe "JOIN" $ do
describe "valid" $ do
it "using same server as in invitation" $
+8 -8
View File
@@ -12,7 +12,7 @@ import Control.Monad.IO.Unlift
import SMPAgentClient
import SMPClient (withSmpServer)
import Simplex.Messaging.Agent
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..))
import Simplex.Messaging.Agent.Env.Postgres (AgentConfig (..))
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Protocol (ErrorType (..), MsgBody)
import Simplex.Messaging.Transport (ATransport (..))
@@ -50,7 +50,7 @@ functionalAPITests t = do
testAgentClient :: IO ()
testAgentClient = do
alice <- getSMPAgentClient cfg
bob <- getSMPAgentClient cfg {dbFile = testDB2}
bob <- getSMPAgentClient cfg {dbConnInfo = testDB2}
Right () <- runExceptT $ do
(bobId, qInfo) <- createConnection alice SCMInvitation
aliceId <- joinConnection bob qInfo "bob's connInfo"
@@ -94,7 +94,7 @@ testAgentClient = do
testAsyncInitiatingOffline :: IO ()
testAsyncInitiatingOffline = do
alice <- getSMPAgentClient cfg
bob <- getSMPAgentClient cfg {dbFile = testDB2}
bob <- getSMPAgentClient cfg {dbConnInfo = testDB2}
Right () <- runExceptT $ do
(bobId, cReq) <- createConnection alice SCMInvitation
disconnectAgentClient alice
@@ -112,14 +112,14 @@ testAsyncInitiatingOffline = do
testAsyncJoiningOfflineBeforeActivation :: IO ()
testAsyncJoiningOfflineBeforeActivation = do
alice <- getSMPAgentClient cfg
bob <- getSMPAgentClient cfg {dbFile = testDB2}
bob <- getSMPAgentClient cfg {dbConnInfo = testDB2}
Right () <- runExceptT $ do
(bobId, qInfo) <- createConnection alice SCMInvitation
aliceId <- joinConnection bob qInfo "bob's connInfo"
disconnectAgentClient bob
("", _, CONF confId "bob's connInfo") <- get alice
allowConnection alice bobId confId "alice's connInfo"
bob' <- liftIO $ getSMPAgentClient cfg {dbFile = testDB2}
bob' <- liftIO $ getSMPAgentClient cfg {dbConnInfo = testDB2}
subscribeConnection bob' aliceId
get alice ##> ("", bobId, CON)
get bob' ##> ("", aliceId, INFO "alice's connInfo")
@@ -130,7 +130,7 @@ testAsyncJoiningOfflineBeforeActivation = do
testAsyncBothOffline :: IO ()
testAsyncBothOffline = do
alice <- getSMPAgentClient cfg
bob <- getSMPAgentClient cfg {dbFile = testDB2}
bob <- getSMPAgentClient cfg {dbConnInfo = testDB2}
Right () <- runExceptT $ do
(bobId, cReq) <- createConnection alice SCMInvitation
disconnectAgentClient alice
@@ -140,7 +140,7 @@ testAsyncBothOffline = do
subscribeConnection alice' bobId
("", _, CONF confId "bob's connInfo") <- get alice'
allowConnection alice' bobId confId "alice's connInfo"
bob' <- liftIO $ getSMPAgentClient cfg {dbFile = testDB2}
bob' <- liftIO $ getSMPAgentClient cfg {dbConnInfo = testDB2}
subscribeConnection bob' aliceId
get alice' ##> ("", bobId, CON)
get bob' ##> ("", aliceId, INFO "alice's connInfo")
@@ -151,7 +151,7 @@ testAsyncBothOffline = do
testAsyncHelloTimeout :: IO ()
testAsyncHelloTimeout = do
alice <- getSMPAgentClient cfg
bob <- getSMPAgentClient cfg {dbFile = testDB2, helloTimeout = 1}
bob <- getSMPAgentClient cfg {dbConnInfo = testDB2, helloTimeout = 1}
Right () <- runExceptT $ do
(_, cReq) <- createConnection alice SCMInvitation
disconnectAgentClient alice
+136
View File
@@ -0,0 +1,136 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
module AgentTests.PostgresTests (postgresStoreTests) where
import Control.Concurrent.Async (concurrently_)
import Control.Concurrent.STM
import Control.Monad (replicateM_)
import Control.Monad.Except (ExceptT, runExceptT)
import Crypto.Random (drgNew)
import Data.ByteString.Char8 (ByteString)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Data.Time
import Data.Word (Word32)
import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo)
import qualified Database.PostgreSQL.Simple as DB
import SMPClient (testKeyHash)
import Simplex.Messaging.Agent.Client ()
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Store
import Simplex.Messaging.Agent.Store.Postgres
import qualified Simplex.Messaging.Agent.Store.Postgres.Migrations as Migrations
import qualified Simplex.Messaging.Crypto as C
import System.Random
import Test.Hspec
import UnliftIO.Directory (removeFile)
withStore :: SpecWith PostgresStore -> Spec
withStore = before createStore
createStore :: IO PostgresStore
createStore = do
let dbConnInfo = defaultConnectInfo {connectDatabase = "agent_poc_1"}
createPostgresStore dbConnInfo 1 Migrations.app
returnsResult :: (Eq a, Eq e, Show a, Show e) => ExceptT e IO a -> a -> Expectation
action `returnsResult` r = runExceptT action `shouldReturn` Right r
throwsError :: (Eq a, Eq e, Show a, Show e) => ExceptT e IO a -> e -> Expectation
action `throwsError` e = runExceptT action `shouldReturn` Left e
-- TODO add null port tests
postgresStoreTests :: Spec
postgresStoreTests = do
-- withStore2 $ do
-- describe "stress test" testConcurrentWrites
withStore $ do
-- describe "store setup" $ do
-- testCompiledThreadsafe
-- testForeignKeysEnabled
describe "store methods" $ do
describe "Queue and Connection management" $ do
-- describe "createRcvConn" $ do
-- testCreateRcvConn
-- testCreateRcvConnRandomId
-- testCreateRcvConnDuplicate
fdescribe "createSndConn" $ do
testCreateSndConn
-- testCreateSndConnRandomID
-- testCreateSndConnDuplicate
-- describe "getRcvConn" testGetRcvConn
-- describe "deleteConn" $ do
-- testDeleteRcvConn
-- testDeleteSndConn
-- testDeleteDuplexConn
-- describe "upgradeRcvConnToDuplex" $ do
-- testUpgradeRcvConnToDuplex
-- describe "upgradeSndConnToDuplex" $ do
-- testUpgradeSndConnToDuplex
-- describe "set Queue status" $ do
-- describe "setRcvQueueStatus" $ do
-- testSetRcvQueueStatus
-- describe "setSndQueueStatus" $ do
-- testSetSndQueueStatus
-- testSetQueueStatusDuplex
-- describe "Msg management" $ do
-- describe "create Msg" $ do
-- testCreateRcvMsg
-- testCreateSndMsg
-- testCreateRcvAndSndMsgs
cData1 :: ConnData
cData1 = ConnData {connId = "conn1"}
testPrivateSignKey :: C.APrivateSignKey
testPrivateSignKey = C.APrivateSignKey C.SEd25519 "MC4CAQAwBQYDK2VwBCIEIDfEfevydXXfKajz3sRkcQ7RPvfWUPoq6pu1TYHV1DEe"
testPrivDhKey :: C.PrivateKeyX25519
testPrivDhKey = "MC4CAQAwBQYDK2VuBCIEINCzbVFaCiYHoYncxNY8tSIfn0pXcIAhLBfFc0m+gOpk"
testDhSecret :: C.DhSecretX25519
testDhSecret = "01234567890123456789012345678901"
rcvQueue1 :: RcvQueue
rcvQueue1 =
RcvQueue
{ server = SMPServer "smp.simplex.im" "5223" testKeyHash,
rcvId = "1234",
rcvPrivateKey = testPrivateSignKey,
rcvDhSecret = testDhSecret,
e2ePrivKey = testPrivDhKey,
e2eDhSecret = Nothing,
sndId = Just "2345",
status = New
}
sndQueue1 :: SndQueue
sndQueue1 =
SndQueue
{ server = SMPServer "smp.simplex.im" "5223" testKeyHash,
sndId = "3456",
sndPrivateKey = testPrivateSignKey,
e2eDhSecret = testDhSecret,
status = New
}
testCreateSndConn :: SpecWith PostgresStore
testCreateSndConn =
it "should create SndConnection and add RcvQueue" $ \store -> do
g <- newTVarIO =<< drgNew
createSndConn store g cData1 sndQueue1
`returnsResult` "conn1"
getConn store "conn1"
`returnsResult` SomeConn SCSnd (SndConnection cData1 sndQueue1)
-- upgradeSndConnToDuplex store "conn1" rcvQueue1
-- `returnsResult` ()
-- getConn store "conn1"
-- `returnsResult` SomeConn SCDuplex (DuplexConnection cData1 rcvQueue1 sndQueue1)
+26 -16
View File
@@ -10,6 +10,7 @@ import Control.Monad.IO.Unlift
import Crypto.Random
import qualified Data.ByteString.Char8 as B
import qualified Data.List.NonEmpty as L
import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo)
import Network.Socket (HostName, ServiceName)
import SMPClient
( serverBracket,
@@ -20,7 +21,7 @@ import SMPClient
withSmpServerOn,
withSmpServerThreadOn,
)
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.Env.Postgres
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Agent.Server (runSMPAgentBlocking)
@@ -43,14 +44,23 @@ agentTestPort2 = "5011"
agentTestPort3 :: ServiceName
agentTestPort3 = "5012"
testDB :: String
testDB = "tests/tmp/smp-agent.test.protocol.db"
-- testDB :: String
-- testDB = "tests/tmp/smp-agent.test.protocol.db"
testDB2 :: String
testDB2 = "tests/tmp/smp-agent2.test.protocol.db"
testDB :: ConnectInfo
testDB = defaultConnectInfo {connectDatabase = "agent_poc_1"}
testDB3 :: String
testDB3 = "tests/tmp/smp-agent3.test.protocol.db"
-- testDB2 :: String
-- testDB2 = "tests/tmp/smp-agent2.test.protocol.db"
testDB2 :: ConnectInfo
testDB2 = defaultConnectInfo {connectDatabase = "agent_poc_2"}
-- testDB3 :: String
-- testDB3 = "tests/tmp/smp-agent3.test.protocol.db"
testDB3 :: ConnectInfo
testDB3 = defaultConnectInfo {connectDatabase = "agent_poc_3"}
smpAgentTest :: forall c. Transport c => TProxy c -> ARawTransmission -> IO ARawTransmission
smpAgentTest _ cmd = runSmpAgentTest $ \(h :: c) -> tPutRaw h cmd >> tGetRaw h
@@ -71,10 +81,10 @@ runSmpAgentServerTest test =
smpAgentServerTest :: Transport c => ((ThreadId, ThreadId) -> c -> IO ()) -> Expectation
smpAgentServerTest test' = runSmpAgentServerTest test' `shouldReturn` ()
runSmpAgentTestN :: forall c m a. (Transport c, MonadUnliftIO m, MonadRandom m) => [(ServiceName, ServiceName, String)] -> ([c] -> m a) -> m a
runSmpAgentTestN :: forall c m a. (Transport c, MonadUnliftIO m, MonadRandom m) => [(ServiceName, ServiceName, ConnectInfo)] -> ([c] -> m a) -> m a
runSmpAgentTestN agents test = withSmpServer t $ run agents []
where
run :: [(ServiceName, ServiceName, String)] -> [c] -> m a
run :: [(ServiceName, ServiceName, ConnectInfo)] -> [c] -> m a
run [] hs = test hs
run (a@(p, _, _) : as) hs = withSmpAgentOn t a $ testSMPAgentClientOn p $ \h -> run as (h : hs)
t = transport @c
@@ -87,7 +97,7 @@ runSmpAgentTestN_1 nClients test = withSmpServer t . withSmpAgent t $ run nClien
run n hs = testSMPAgentClient $ \h -> run (n - 1) (h : hs)
t = transport @c
smpAgentTestN :: Transport c => [(ServiceName, ServiceName, String)] -> ([c] -> IO ()) -> Expectation
smpAgentTestN :: Transport c => [(ServiceName, ServiceName, ConnectInfo)] -> ([c] -> IO ()) -> Expectation
smpAgentTestN agents test' = runSmpAgentTestN agents test' `shouldReturn` ()
smpAgentTestN_1 :: Transport c => Int -> ([c] -> IO ()) -> Expectation
@@ -159,7 +169,7 @@ cfg =
{ tcpPort = agentTestPort,
smpServers = L.fromList ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:5001"],
tbqSize = 1,
dbFile = testDB,
dbConnInfo = testDB,
smpCfg =
smpDefaultConfig
{ qSize = 1,
@@ -172,17 +182,17 @@ cfg =
certificateFile = "tests/fixtures/server.crt"
}
withSmpAgentThreadOn_ :: (MonadUnliftIO m, MonadRandom m) => ATransport -> (ServiceName, ServiceName, String) -> m () -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn_ :: (MonadUnliftIO m, MonadRandom m) => ATransport -> (ServiceName, ServiceName, ConnectInfo) -> m () -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn_ t (port', smpPort', db') afterProcess =
let cfg' = cfg {tcpPort = port', dbFile = db', smpServers = L.fromList [SMPServer "localhost" smpPort' testKeyHash]}
let cfg' = cfg {tcpPort = port', dbConnInfo = db', smpServers = L.fromList [SMPServer "localhost" smpPort' testKeyHash]}
in serverBracket
(\started -> runSMPAgentBlocking t started cfg')
afterProcess
withSmpAgentThreadOn :: (MonadUnliftIO m, MonadRandom m) => ATransport -> (ServiceName, ServiceName, String) -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn t a@(_, _, db') = withSmpAgentThreadOn_ t a $ removeFile db'
withSmpAgentThreadOn :: (MonadUnliftIO m, MonadRandom m) => ATransport -> (ServiceName, ServiceName, ConnectInfo) -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn t a@(_, _, db') = withSmpAgentThreadOn_ t a $ pure () -- $ removeFile db'
withSmpAgentOn :: (MonadUnliftIO m, MonadRandom m) => ATransport -> (ServiceName, ServiceName, String) -> m a -> m a
withSmpAgentOn :: (MonadUnliftIO m, MonadRandom m) => ATransport -> (ServiceName, ServiceName, ConnectInfo) -> m a -> m a
withSmpAgentOn t (port', smpPort', db') = withSmpAgentThreadOn t (port', smpPort', db') . const
withSmpAgent :: (MonadUnliftIO m, MonadRandom m) => ATransport -> m a -> m a