SMP router specs

This commit is contained in:
Evgeny @ SimpleX Chat
2026-03-12 11:29:18 +00:00
parent 09d55de115
commit 260ffb1a9d
33 changed files with 715 additions and 0 deletions
+4
View File
@@ -247,6 +247,7 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg, startOpt
closeServer :: M s ()
closeServer = asks (smpAgent . proxyAgent) >>= liftIO . closeSMPClientAgent
-- spec: spec/modules/Simplex/Messaging/Server.md#serverthread--subscription-lifecycle-with-split-stm
serverThread ::
forall sub. String ->
Server s ->
@@ -1223,6 +1224,7 @@ disconnectTransport THandle {connection, params = THandleParams {sessionId}} rcv
data VerificationResult s = VRVerified (Maybe (StoreQueue s, QueueRec)) | VRFailed ErrorType
-- spec: spec/modules/Simplex/Messaging/Server.md#constant-time-authorization--dummy-keys
-- This function verifies queue command authorization, with the objective to have constant time between the three AUTH error scenarios:
-- - the queue and party key exist, and the provided authorization has type matching queue key, but it is made with the different key.
-- - the queue and party key exist, but the provided authorization has incorrect type.
@@ -1982,6 +1984,7 @@ client
-- If the queue is not full, then the thread is created where these checks are made:
-- - it is the same subscribed client (in case it was reconnected it would receive message via SUB command)
-- - nothing was delivered to this subscription (to avoid race conditions with the recipient).
-- spec: spec/modules/Simplex/Messaging/Server.md#trydelivermessage--syncasync-split-delivery
tryDeliverMessage :: Message -> IO ()
tryDeliverMessage msg =
-- the subscribed client var is read outside of STM to avoid transaction cost
@@ -2063,6 +2066,7 @@ client
encNMsgMeta = C.cbEncrypt rcvNtfDhSecret ntfNonce (smpEncode msgMeta) 128
pure $ MsgNtf {ntfMsgId = msgId, ntfTs = msgTs, ntfNonce, ntfEncMeta = fromRight "" encNMsgMeta}
-- spec: spec/modules/Simplex/Messaging/Server.md#proxy-forwarding--single-transmission-no-service-identity
processForwardedCommand :: EncFwdTransmission -> M s BrokerMsg
processForwardedCommand (EncFwdTransmission s) = fmap (either ERR RRES) . runExceptT $ do
THAuthServer {serverPrivKey, sessSecret'} <- maybe (throwE $ transportErr TENoServerAuth) pure (thAuth thParams')
+1
View File
@@ -368,6 +368,7 @@ data ServerSubscribers s = ServerSubscribers
pendingEvents :: TVar (IntMap (NonEmpty (EntityId, BrokerMsg)))
}
-- spec: spec/modules/Simplex/Messaging/Server/Env/STM.md#subscribedclients--tvar-of-maybe-pattern
-- not exported, to prevent accidental concurrent Map lookups inside STM transactions.
-- Map stores TVars with pointers to the clients rather than client ID to allow reading the same TVar
-- inside transactions to ensure that transaction is re-evaluated in case subscriber changes.
@@ -76,6 +76,7 @@ data PostgresQueue = PostgresQueue
queueRec' :: TVar (Maybe QueueRec)
}
-- spec: spec/modules/Simplex/Messaging/Server/MsgStore/Postgres.md#msgqueue-is-unit-type
instance StoreQueueClass PostgresQueue where
recipientId = recipientId'
{-# INLINE recipientId #-}
@@ -49,6 +49,7 @@ import Simplex.Messaging.Server.QueueStore
import Simplex.Messaging.Server.QueueStore.Types
import Simplex.Messaging.Util ((<$$>), ($>>=))
-- spec: spec/modules/Simplex/Messaging/Server/MsgStore/Types.md#injective-type-families--unambiguous-type-resolution
class (Monad (StoreMonad s), QueueStoreClass (StoreQueue s) (QueueStore s)) => MsgStoreClass s where
type StoreMonad s = (m :: Type -> Type) | m -> s
type MsgStoreConfig s = c | c -> s
@@ -169,6 +169,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where
(SRMessaging, SRNotifier)
pure EntityCounts {queueCount, notifierCount, rcvServiceCount, ntfServiceCount, rcvServiceQueuesCount, ntfServiceQueuesCount}
-- spec: spec/modules/Simplex/Messaging/Server/QueueStore/Postgres.md#addqueue_--no-in-memory-duplicate-check-relies-on-db-constraint
-- this implementation assumes that the lock is already taken by addQueue
-- and relies on unique constraints in the database to prevent duplicate IDs.
addQueue_ :: PostgresQueueStore q -> (RecipientId -> QueueRec -> IO q) -> RecipientId -> QueueRec -> IO (Either ErrorType q)
@@ -116,6 +116,7 @@ instance StoreQueueClass q => QueueStoreClass q (STMQueueStore q) where
serviceCount role = M.foldl' (\ !n s -> if serviceRole (serviceRec s) == role then n + 1 else n) 0
serviceQueuesCount serviceSel = foldM (\n s -> (n +) . S.size . fst <$> readTVarIO (serviceSel s)) 0
-- spec: spec/modules/Simplex/Messaging/Server/QueueStore/STM.md#addqueue_--atomic-multi-id-duplicate-check
addQueue_ :: STMQueueStore q -> (RecipientId -> QueueRec -> IO q) -> RecipientId -> QueueRec -> IO (Either ErrorType q)
addQueue_ st mkQ rId qr@QueueRec {senderId = sId, notifier, queueData, rcvServiceId} = do
sq <- mkQ rId qr
+3
View File
@@ -96,6 +96,7 @@ data SLRTag
| NewService_
| QueueService_
-- spec: spec/modules/Simplex/Messaging/Server/StoreLog.md#queuerec-strencoding--backward-compatible-parsing
instance StrEncoding QueueRec where
strEncode QueueRec {recipientKeys, rcvDhSecret, rcvServiceId, senderId, senderKey, queueMode, queueData, notifier, status, updatedAt} =
B.concat
@@ -242,6 +243,7 @@ closeStoreLog = \case
where
close_ h = hClose h `catchAny` \e -> logError ("STORE: closeStoreLog, error closing, " <> tshow e)
-- spec: spec/modules/Simplex/Messaging/Server/StoreLog.md#writestorelogrecord--atomicity-via-manual-write
writeStoreLogRecord :: StrEncoding r => StoreLog 'WriteMode -> r -> IO ()
writeStoreLogRecord (WriteStoreLog _ h) r = E.uninterruptibleMask_ $ do
B.hPut h $ strEncode r `B.snoc` '\n' -- hPutStrLn makes write non-atomic for length > 1024
@@ -289,6 +291,7 @@ logNewService s = writeStoreLogRecord s . NewService
logQueueService :: (PartyI p, ServiceParty p) => StoreLog 'WriteMode -> RecipientId -> SParty p -> Maybe ServiceId -> IO ()
logQueueService s rId party = writeStoreLogRecord s . QueueService rId (ASP party)
-- spec: spec/modules/Simplex/Messaging/Server/StoreLog.md#readwritestorelog--crash-recovery-state-machine
readWriteStoreLog :: (FilePath -> s -> IO ()) -> (StoreLog 'WriteMode -> s -> IO ()) -> FilePath -> s -> IO (StoreLog 'WriteMode)
readWriteStoreLog readStore writeStore f st =
ifM