mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-05-24 23:26:00 +00:00
smp server: messaging services (#1565)
* smp server: refactor message delivery to always respond SOK to subscriptions * refactor ntf subscribe * cancel subscription thread and reduce service subscription count when queue is deleted * subscribe rcv service, deliver sent messages to subscribed service * subscribe rcv service to messages (TODO delivery on subscription) * WIP * efficient initial delivery of messages to subscribed service * test: delivery to client with service certificate * test: upgrade/downgrade to/from service subscriptions * remove service association from agent API, add per-user flag to use the service * agent client (WIP) * service certificates in the client * rfc about drift detection, and SALL to mark end of message delivery * fix test * fix test * add function for postgresql message storage * update migration
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Detecting and fixing state with service subscriptions
|
||||
|
||||
## Problem
|
||||
|
||||
While service certificates and subscriptions hugely decrease startup time and delivery delays on server restarts, they introduce the risk of losing subscriptions in case of state drifts. They also do not provide efficient mechanism for validating that the list of subscribed queues is in sync.
|
||||
|
||||
How can the state drift happen?
|
||||
|
||||
There are several possibilities:
|
||||
- lost broker response would make the broker consider that the queue is associated, but the client won't know it, and will have to re-associate. While in itself it is not a problem, as it'll be resolved, it would make drift detected more frequently (regardless of the detection logic used). That service certificates are used on clients with good connection would make it less likely though.
|
||||
- server state restored from the backup, in case of some failure. Nothing can be done to recover lost queues, but we may restore lost service associations.
|
||||
- queue blocking or removal by server operator because of policy violation.
|
||||
- server downgrade (when it loses all service associations) with subsequent upgrade - the client would think queues are associated, while they are not, and won't receive any messages at all in this scenario.
|
||||
- any other server-side error or logic error.
|
||||
|
||||
In addition to the possibility of the drift, we simply need to have confidence that service subscriptions work as intended, without skipping queues. We ignored this consideration for notifications, as the tolerance to lost notifications is higher, but we can't ignore it for messages.
|
||||
|
||||
## Solution
|
||||
|
||||
Previously considered approach of sending NIL to all queues without messages is very expensive for traffic (most queues don't have messages), and it is also very expensive to detect and validate drift in the client because of asynchronous / concurrent events.
|
||||
|
||||
We cannot read all queues into memory, and we cannot aggregate all responses in memory, and we cannot create database writes on every single service subscription to say 1m queues (a realistic number), as it simply won't work well even at the current scale.
|
||||
|
||||
An approach of having an efficient way to detect drift, but load the full list of IDs when drift is detected, also won't work well, as drifts may be common, so we need both efficient way to detect there is diff and also to reconcile it.
|
||||
|
||||
### Drift detection
|
||||
|
||||
Both client and server would maintain the number of associated queues and the "symmetric" hash over the set of queue IDs. The requirements for this hash algorithm are:
|
||||
- not cryptographically strong, to be fast.
|
||||
- 128 bits to minimize collisions over the large set of millions of queues.
|
||||
- symmetric - the result should not depend on ID order.
|
||||
- allows fast additions and removals.
|
||||
|
||||
In this way, every time association is added or removed (including queue marked as deleted), both peers would recompute this hash in the same transaction.
|
||||
|
||||
The client would suspend sending and processing any other commands on the server and the queues of this server until SOKS response is received from this server, to prevent drift. It can be achieved with per-server semaphores/locks in memory. UI clients need to become responsive sooner than these responses are received, but we do not service certificates on UI clients, and chat relays may prevent operations on server queues until SOKS response is received.
|
||||
|
||||
SOKS response would include both the count of associated queues (as now) and the hash over all associated queue IDs (to be added). If both count and hash match, the client will not do anything. If either does not match the client would perform full sync (see below).
|
||||
|
||||
There is a value from doing the same in notification server as well to detect and "fix" drifts.
|
||||
|
||||
The algorithm to compute hashes can be the following.
|
||||
|
||||
1. Compute hash of each queue ID using xxHash3_128 ([xxhash-ffi](https://hackage.haskell.org/package/xxhash-ffi) library). They don't need to be stored or loaded at once, initially, it can be done with streaming if it is detected on start that there is no pre-computed hash.
|
||||
2. Combine hashes using XOR. XOR is both commutative and associative, so it would produce the same aggregate hash irrespective of the ID order.
|
||||
3. Adding queue ID to pre-computed hash requires a single XOR with ID hash: `new_aggregate = aggregate XOR hash(queue_id)`.
|
||||
4. Removing queue ID from pre-computed hash also requires the same XOR (XOR is involutory, it undoes itself): `new_aggregate = aggregate XOR hash(queue_id)`.
|
||||
|
||||
These hashes need to be computed per user/server in the client and per service certificate in the server - on startup both have to validate and compute them once if necessary.
|
||||
|
||||
There can be also a start-up option to recompute hashe(s) to detect and fix any errors.
|
||||
|
||||
This is all rather simple and would help detecting drifts.
|
||||
|
||||
### Synchronization when drift is detected
|
||||
|
||||
The assumption here is that in most cases drifts are rare, and isolated to few IDs (e.g., this is the case with notification server).
|
||||
|
||||
But the algorithm should be resilient to losing all associations, and it should not be substantially worse than simply restoring all associations or loading all IDs.
|
||||
|
||||
We have `c_n` and `c_hash` for client-side count and hash of queue IDs and `s_n` and `s_hash` for server-side, which are returned in SOKS response to SUBS command.
|
||||
|
||||
1. If `c_n /= s_n || c_hash /= s_hash`, the client must perform sync.
|
||||
|
||||
2. If `abs(c_n - s_n) / max(c_n, s_n) > 0.5`, the client will request the full list of queues (more than half of the queues are different), and will perform diff with the queues it has. While performing the diff the client will continue block operations with this user/server.
|
||||
|
||||
3. Otherwise would perform some algorithm for determining the difference between queue IDs between client and server. This algorithm can be made efficient (`O(log N)`) by relying on efficient sorting of IDs and database loading of ranges, via computing and communicating hashes of ranges, and performing a binary search on ranges, with batching to optimize network traffic.
|
||||
|
||||
This algorithm is similar to Merkle tree reconcilliation, but it is optimized for database reading of ordered ranges, and for our 16kb block size to minimize network requests.
|
||||
|
||||
The algorithm:
|
||||
1. The client would request all ranges from the server.
|
||||
2. The server would compute hashes for N ranges of IDs and send them to the client. Each range would include start_id, optional end_id (for single ID ranges) and XOR-hash of the range. N is determined based on the block size and the range size.
|
||||
3. The client would perform the same computation for the same ranges, and compare them with the returned ranges from the server, while detecting any gaps between ranges and missing range boundaries.
|
||||
4. If more than half of the ranges don't match, the client would request the full list. Otherwise it would repeat the same algorithm for each mismatched range and for gaps.
|
||||
|
||||
It can be further optimized by merging adjacent ranges and by batching all range requests, it is quite simple.
|
||||
|
||||
Once the client determines the list of missing and extra queues it can:
|
||||
- create associations (via SUB) for missing queues,
|
||||
- request removal of association (a new command, e.g. BUS) for extra queues on the server.
|
||||
|
||||
The pseudocode for the algorightm:
|
||||
|
||||
For the server to return all ranges or subranges of requested range:
|
||||
|
||||
```haskell
|
||||
getSubRanges :: Maybe (RecipientId, RecipientId) -> [(RecipientId, Maybe RecipientId, Hash)]
|
||||
getSubRanges range_ = do
|
||||
((min_id, max_id), s_n) <- case range_ of
|
||||
Nothing -> getAssociatedQueueRange -- with the certificate in the client session.
|
||||
Just range -> (range,) <$> getAssociatedQueueCount range
|
||||
if
|
||||
| s_n <= max_N -> reply_with_single_queue_ranges
|
||||
| otherwise -> do
|
||||
let range_size = s_n `div` max_N
|
||||
read_all_ranges -- in a recursive loop, with max_id, range_hash and next_min_id in each step
|
||||
reply_ranges
|
||||
```
|
||||
|
||||
We don't need to implement this synchronization logic right now, so not including client logic here, it's sufficient to implement drift detection, and the action to fix the drift would be to disable and to re-enable certificates via some command-line parameter of CLI.
|
||||
@@ -216,6 +216,7 @@ library
|
||||
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250702_conn_invitations_remove_cascade_delete
|
||||
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251009_queue_to_subscribe
|
||||
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251010_client_notices
|
||||
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251020_service_certs
|
||||
if flag(client_postgres) || flag(server_postgres)
|
||||
exposed-modules:
|
||||
Simplex.Messaging.Agent.Store.Postgres
|
||||
@@ -553,6 +554,7 @@ test-suite simplexmq-test
|
||||
, text
|
||||
, time
|
||||
, timeit ==2.0.*
|
||||
, tls >=1.9.0 && <1.10
|
||||
, transformers
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
|
||||
@@ -47,6 +47,7 @@ module Simplex.Messaging.Agent
|
||||
withInvLock,
|
||||
createUser,
|
||||
deleteUser,
|
||||
setUserService,
|
||||
connRequestPQSupport,
|
||||
createConnectionAsync,
|
||||
joinConnectionAsync,
|
||||
@@ -78,7 +79,7 @@ module Simplex.Messaging.Agent
|
||||
getNotificationConns,
|
||||
resubscribeConnection,
|
||||
resubscribeConnections,
|
||||
subscribeClientService,
|
||||
subscribeClientServices,
|
||||
sendMessage,
|
||||
sendMessages,
|
||||
sendMessagesB,
|
||||
@@ -210,6 +211,7 @@ import Simplex.Messaging.Protocol
|
||||
ErrorType (AUTH),
|
||||
MsgBody,
|
||||
MsgFlags (..),
|
||||
IdsHash,
|
||||
NtfServer,
|
||||
ProtoServerWithAuth (..),
|
||||
ProtocolServer (..),
|
||||
@@ -340,6 +342,11 @@ deleteUser :: AgentClient -> UserId -> Bool -> AE ()
|
||||
deleteUser c = withAgentEnv c .: deleteUser' c
|
||||
{-# INLINE deleteUser #-}
|
||||
|
||||
-- | Enable using service certificate for this user
|
||||
setUserService :: AgentClient -> UserId -> Bool -> AE ()
|
||||
setUserService c = withAgentEnv c .: setUserService' c
|
||||
{-# INLINE setUserService #-}
|
||||
|
||||
-- | Create SMP agent connection (NEW command) asynchronously, synchronous response is new connection id
|
||||
createConnectionAsync :: ConnectionModeI c => AgentClient -> UserId -> ACorrId -> Bool -> SConnectionMode c -> CR.InitialKeys -> SubscriptionMode -> AE ConnId
|
||||
createConnectionAsync c userId aCorrId enableNtfs = withAgentEnv c .:. newConnAsync c userId aCorrId enableNtfs
|
||||
@@ -381,7 +388,7 @@ deleteConnectionsAsync c waitDelivery = withAgentEnv c . deleteConnectionsAsync'
|
||||
{-# INLINE deleteConnectionsAsync #-}
|
||||
|
||||
-- | Create SMP agent connection (NEW command)
|
||||
createConnection :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> Bool -> SConnectionMode c -> Maybe (UserConnLinkData c) -> Maybe CRClientData -> CR.InitialKeys -> SubscriptionMode -> AE (ConnId, (CreatedConnLink c, Maybe ClientServiceId))
|
||||
createConnection :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> Bool -> SConnectionMode c -> Maybe (UserConnLinkData c) -> Maybe CRClientData -> CR.InitialKeys -> SubscriptionMode -> AE (ConnId, CreatedConnLink c)
|
||||
createConnection c nm userId enableNtfs checkNotices = withAgentEnv c .::. newConn c nm userId enableNtfs checkNotices
|
||||
{-# INLINE createConnection #-}
|
||||
|
||||
@@ -424,7 +431,7 @@ prepareConnectionToAccept c userId enableNtfs = withAgentEnv c .: newConnToAccep
|
||||
{-# INLINE prepareConnectionToAccept #-}
|
||||
|
||||
-- | Join SMP agent connection (JOIN command).
|
||||
joinConnection :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AE (SndQueueSecured, Maybe ClientServiceId)
|
||||
joinConnection :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AE SndQueueSecured
|
||||
joinConnection c nm userId connId enableNtfs = withAgentEnv c .:: joinConn c nm userId connId enableNtfs
|
||||
{-# INLINE joinConnection #-}
|
||||
|
||||
@@ -434,7 +441,7 @@ allowConnection c = withAgentEnv c .:. allowConnection' c
|
||||
{-# INLINE allowConnection #-}
|
||||
|
||||
-- | Accept contact after REQ notification (ACPT command)
|
||||
acceptContact :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConfirmationId -> ConnInfo -> PQSupport -> SubscriptionMode -> AE (SndQueueSecured, Maybe ClientServiceId)
|
||||
acceptContact :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConfirmationId -> ConnInfo -> PQSupport -> SubscriptionMode -> AE SndQueueSecured
|
||||
acceptContact c userId connId enableNtfs = withAgentEnv c .::. acceptContact' c userId connId enableNtfs
|
||||
{-# INLINE acceptContact #-}
|
||||
|
||||
@@ -462,12 +469,12 @@ syncConnections c = withAgentEnv c .: syncConnections' c
|
||||
{-# INLINE syncConnections #-}
|
||||
|
||||
-- | Subscribe to receive connection messages (SUB command)
|
||||
subscribeConnection :: AgentClient -> ConnId -> AE (Maybe ClientServiceId)
|
||||
subscribeConnection :: AgentClient -> ConnId -> AE ()
|
||||
subscribeConnection c = withAgentEnv c . subscribeConnection' c
|
||||
{-# INLINE subscribeConnection #-}
|
||||
|
||||
-- | Subscribe to receive connection messages from multiple connections, batching commands when possible
|
||||
subscribeConnections :: AgentClient -> [ConnId] -> AE (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
|
||||
subscribeConnections :: AgentClient -> [ConnId] -> AE (Map ConnId (Either AgentErrorType ()))
|
||||
subscribeConnections c = withAgentEnv c . subscribeConnections' c
|
||||
{-# INLINE subscribeConnections #-}
|
||||
|
||||
@@ -485,18 +492,17 @@ getNotificationConns :: AgentClient -> C.CbNonce -> ByteString -> AE (NonEmpty N
|
||||
getNotificationConns c = withAgentEnv c .: getNotificationConns' c
|
||||
{-# INLINE getNotificationConns #-}
|
||||
|
||||
resubscribeConnection :: AgentClient -> ConnId -> AE (Maybe ClientServiceId)
|
||||
resubscribeConnection :: AgentClient -> ConnId -> AE ()
|
||||
resubscribeConnection c = withAgentEnv c . resubscribeConnection' c
|
||||
{-# INLINE resubscribeConnection #-}
|
||||
|
||||
resubscribeConnections :: AgentClient -> [ConnId] -> AE (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
|
||||
resubscribeConnections :: AgentClient -> [ConnId] -> AE (Map ConnId (Either AgentErrorType ()))
|
||||
resubscribeConnections c = withAgentEnv c . resubscribeConnections' c
|
||||
{-# INLINE resubscribeConnections #-}
|
||||
|
||||
-- TODO [certs rcv] how to communicate that service ID changed - as error or as result?
|
||||
subscribeClientService :: AgentClient -> ClientServiceId -> AE Int
|
||||
subscribeClientService c = withAgentEnv c . subscribeClientService' c
|
||||
{-# INLINE subscribeClientService #-}
|
||||
subscribeClientServices :: AgentClient -> UserId -> AE (Map SMPServer (Either AgentErrorType (Int64, IdsHash)))
|
||||
subscribeClientServices c = withAgentEnv c . subscribeClientServices' c
|
||||
{-# INLINE subscribeClientServices #-}
|
||||
|
||||
-- | Send message to the connection (SEND command)
|
||||
sendMessage :: AgentClient -> ConnId -> PQEncryption -> MsgFlags -> MsgBody -> AE (AgentMsgId, PQEncryption)
|
||||
@@ -746,6 +752,7 @@ createUser' c smp xftp = do
|
||||
userId <- withStore' c createUserRecord
|
||||
atomically $ TM.insert userId (mkUserServers smp) $ smpServers c
|
||||
atomically $ TM.insert userId (mkUserServers xftp) $ xftpServers c
|
||||
atomically $ TM.insert userId False $ useClientServices c
|
||||
pure userId
|
||||
|
||||
deleteUser' :: AgentClient -> UserId -> Bool -> AM ()
|
||||
@@ -755,6 +762,7 @@ deleteUser' c@AgentClient {smpServersStats, xftpServersStats} userId delSMPQueue
|
||||
else withStore c (`deleteUserRecord` userId)
|
||||
atomically $ TM.delete userId $ smpServers c
|
||||
atomically $ TM.delete userId $ xftpServers c
|
||||
atomically $ TM.delete userId $ useClientServices c
|
||||
atomically $ modifyTVar' smpServersStats $ M.filterWithKey (\(userId', _) _ -> userId' /= userId)
|
||||
atomically $ modifyTVar' xftpServersStats $ M.filterWithKey (\(userId', _) _ -> userId' /= userId)
|
||||
lift $ saveServersStats c
|
||||
@@ -763,6 +771,13 @@ deleteUser' c@AgentClient {smpServersStats, xftpServersStats} userId delSMPQueue
|
||||
whenM (withStore' c (`deleteUserWithoutConns` userId)) . atomically $
|
||||
writeTBQueue (subQ c) ("", "", AEvt SAENone $ DEL_USER userId)
|
||||
|
||||
setUserService' :: AgentClient -> UserId -> Bool -> AM ()
|
||||
setUserService' c userId enable = do
|
||||
wasEnabled <- liftIO $ fromMaybe False <$> TM.lookupIO userId (useClientServices c)
|
||||
when (enable /= wasEnabled) $ do
|
||||
atomically $ TM.insert userId enable $ useClientServices c
|
||||
unless enable $ withStore' c (`deleteClientServices` userId)
|
||||
|
||||
newConnAsync :: ConnectionModeI c => AgentClient -> UserId -> ACorrId -> Bool -> SConnectionMode c -> CR.InitialKeys -> SubscriptionMode -> AM ConnId
|
||||
newConnAsync c userId corrId enableNtfs cMode pqInitKeys subMode = do
|
||||
connId <- newConnNoQueues c userId enableNtfs cMode (CR.connPQEncryption pqInitKeys)
|
||||
@@ -865,7 +880,7 @@ switchConnectionAsync' c corrId connId =
|
||||
connectionStats c $ DuplexConnection cData rqs' sqs
|
||||
_ -> throwE $ CMD PROHIBITED "switchConnectionAsync: not duplex"
|
||||
|
||||
newConn :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> Bool -> SConnectionMode c -> Maybe (UserConnLinkData c) -> Maybe CRClientData -> CR.InitialKeys -> SubscriptionMode -> AM (ConnId, (CreatedConnLink c, Maybe ClientServiceId))
|
||||
newConn :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> Bool -> SConnectionMode c -> Maybe (UserConnLinkData c) -> Maybe CRClientData -> CR.InitialKeys -> SubscriptionMode -> AM (ConnId, CreatedConnLink c)
|
||||
newConn c nm userId enableNtfs checkNotices cMode linkData_ clientData pqInitKeys subMode = do
|
||||
srv <- getSMPServer c userId
|
||||
when (checkNotices && connMode cMode == CMContact) $ checkClientNotices c srv
|
||||
@@ -989,7 +1004,7 @@ changeConnectionUser' c oldUserId connId newUserId = do
|
||||
where
|
||||
updateConn = withStore' c $ \db -> setConnUserId db oldUserId connId newUserId
|
||||
|
||||
newRcvConnSrv :: forall c. ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> SConnectionMode c -> Maybe (UserConnLinkData c) -> Maybe CRClientData -> CR.InitialKeys -> SubscriptionMode -> SMPServerWithAuth -> AM (CreatedConnLink c, Maybe ClientServiceId)
|
||||
newRcvConnSrv :: forall c. ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> SConnectionMode c -> Maybe (UserConnLinkData c) -> Maybe CRClientData -> CR.InitialKeys -> SubscriptionMode -> SMPServerWithAuth -> AM (CreatedConnLink c)
|
||||
newRcvConnSrv c nm userId connId enableNtfs cMode userLinkData_ clientData pqInitKeys subMode srvWithAuth@(ProtoServerWithAuth srv _) = do
|
||||
case (cMode, pqInitKeys) of
|
||||
(SCMContact, CR.IKUsePQ) -> throwE $ CMD PROHIBITED "newRcvConnSrv"
|
||||
@@ -1000,12 +1015,12 @@ newRcvConnSrv c nm userId connId enableNtfs cMode userLinkData_ clientData pqIni
|
||||
(nonce, qUri, cReq, qd) <- prepareLinkData d $ fst e2eKeys
|
||||
(rq, qUri') <- createRcvQueue (Just nonce) qd e2eKeys
|
||||
ccLink <- connReqWithShortLink qUri cReq qUri' (shortLink rq)
|
||||
pure (ccLink, clientServiceId rq)
|
||||
pure ccLink
|
||||
Nothing -> do
|
||||
let qd = case cMode of SCMContact -> CQRContact Nothing; SCMInvitation -> CQRMessaging Nothing
|
||||
(rq, qUri) <- createRcvQueue Nothing qd e2eKeys
|
||||
(_rq, qUri) <- createRcvQueue Nothing qd e2eKeys
|
||||
cReq <- createConnReq qUri
|
||||
pure (CCLink cReq Nothing, clientServiceId rq)
|
||||
pure $ CCLink cReq Nothing
|
||||
where
|
||||
createRcvQueue :: Maybe C.CbNonce -> ClntQueueReqData -> C.KeyPairX25519 -> AM (RcvQueue, SMPQueueUri)
|
||||
createRcvQueue nonce_ qd e2eKeys = do
|
||||
@@ -1107,7 +1122,7 @@ newConnToAccept c userId connId enableNtfs invId pqSup = do
|
||||
Invitation {connReq} <- withStore c $ \db -> getInvitation db "newConnToAccept" invId
|
||||
newConnToJoin c userId connId enableNtfs connReq pqSup
|
||||
|
||||
joinConn :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
joinConn :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AM SndQueueSecured
|
||||
joinConn c nm userId connId enableNtfs cReq cInfo pqSupport subMode = do
|
||||
srv <- getNextSMPServer c userId [qServer $ connReqQueue cReq]
|
||||
joinConnSrv c nm userId connId enableNtfs cReq cInfo pqSupport subMode srv
|
||||
@@ -1187,7 +1202,7 @@ versionPQSupport_ :: VersionSMPA -> Maybe CR.VersionE2E -> PQSupport
|
||||
versionPQSupport_ agentV e2eV_ = PQSupport $ agentV >= pqdrSMPAgentVersion && maybe True (>= CR.pqRatchetE2EEncryptVersion) e2eV_
|
||||
{-# INLINE versionPQSupport_ #-}
|
||||
|
||||
joinConnSrv :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> SMPServerWithAuth -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
joinConnSrv :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> SMPServerWithAuth -> AM SndQueueSecured
|
||||
joinConnSrv c nm userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSup subMode srv =
|
||||
withInvLock c (strEncode inv) "joinConnSrv" $ do
|
||||
SomeConn cType conn <- withStore c (`getConn` connId)
|
||||
@@ -1198,7 +1213,7 @@ joinConnSrv c nm userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSup sub
|
||||
| sqStatus == New || sqStatus == Secured -> doJoin (Just rq) (Just sq)
|
||||
_ -> throwE $ CMD PROHIBITED $ "joinConnSrv: bad connection " <> show cType
|
||||
where
|
||||
doJoin :: Maybe RcvQueue -> Maybe SndQueue -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
doJoin :: Maybe RcvQueue -> Maybe SndQueue -> AM SndQueueSecured
|
||||
doJoin rq_ sq_ = do
|
||||
(cData, sq, e2eSndParams, lnkId_) <- startJoinInvitation c userId connId sq_ enableNtfs inv pqSup
|
||||
secureConfirmQueue c nm cData rq_ sq srv cInfo (Just e2eSndParams) subMode
|
||||
@@ -1209,14 +1224,14 @@ joinConnSrv c nm userId connId enableNtfs cReqUri@CRContactUri {} cInfo pqSup su
|
||||
withInvLock c (strEncode cReqUri) "joinConnSrv" $ do
|
||||
SomeConn cType conn <- withStore c (`getConn` connId)
|
||||
let pqInitKeys = CR.joinContactInitialKeys (v >= pqdrSMPAgentVersion) pqSup
|
||||
(CCLink cReq _, service) <- case conn of
|
||||
CCLink cReq _ <- case conn of
|
||||
NewConnection _ -> newRcvConnSrv c NRMBackground userId connId enableNtfs SCMInvitation Nothing Nothing pqInitKeys subMode srv
|
||||
RcvConnection _ rq -> mkJoinInvitation rq pqInitKeys
|
||||
_ -> throwE $ CMD PROHIBITED $ "joinConnSrv: bad connection " <> show cType
|
||||
void $ sendInvitation c nm userId connId qInfo vrsn cReq cInfo
|
||||
pure (False, service)
|
||||
pure False
|
||||
where
|
||||
mkJoinInvitation rq@RcvQueue {clientService} pqInitKeys = do
|
||||
mkJoinInvitation rq pqInitKeys = do
|
||||
g <- asks random
|
||||
AgentConfig {smpClientVRange = vr, smpAgentVRange, e2eEncryptVRange = e2eVR} <- asks config
|
||||
let qUri = SMPQueueUri vr $ (rcvSMPQueueAddress rq) {queueMode = Just QMMessaging}
|
||||
@@ -1231,7 +1246,7 @@ joinConnSrv c nm userId connId enableNtfs cReqUri@CRContactUri {} cInfo pqSup su
|
||||
createRatchetX3dhKeys db connId pk1 pk2 pKem
|
||||
pure e2eRcvParams
|
||||
let cReq = CRInvitationUri crData $ toVersionRangeT e2eRcvParams e2eVR
|
||||
pure (CCLink cReq Nothing, dbServiceId <$> clientService)
|
||||
pure $ CCLink cReq Nothing
|
||||
Nothing -> throwE $ AGENT A_VERSION
|
||||
|
||||
delInvSL :: AgentClient -> ConnId -> SMPServerWithAuth -> SMP.LinkId -> AM ()
|
||||
@@ -1239,7 +1254,7 @@ delInvSL c connId srv lnkId =
|
||||
withStore' c (\db -> deleteInvShortLink db (protoServer srv) lnkId) `catchE` \e ->
|
||||
liftIO $ nonBlockingWriteTBQueue (subQ c) ("", connId, AEvt SAEConn (ERR $ INTERNAL $ "error deleting short link " <> show e))
|
||||
|
||||
joinConnSrvAsync :: AgentClient -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> SMPServerWithAuth -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
joinConnSrvAsync :: AgentClient -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> SMPServerWithAuth -> AM SndQueueSecured
|
||||
joinConnSrvAsync c userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSupport subMode srv = do
|
||||
SomeConn cType conn <- withStore c (`getConn` connId)
|
||||
case conn of
|
||||
@@ -1251,7 +1266,7 @@ joinConnSrvAsync c userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSuppo
|
||||
| sqStatus == New || sqStatus == Secured -> doJoin (Just rq) (Just sq)
|
||||
_ -> throwE $ CMD PROHIBITED $ "joinConnSrvAsync: bad connection " <> show cType
|
||||
where
|
||||
doJoin :: Maybe RcvQueue -> Maybe SndQueue -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
doJoin :: Maybe RcvQueue -> Maybe SndQueue -> AM SndQueueSecured
|
||||
doJoin rq_ sq_ = do
|
||||
(cData, sq, e2eSndParams, lnkId_) <- startJoinInvitation c userId connId sq_ enableNtfs inv pqSupport
|
||||
secureConfirmQueueAsync c cData rq_ sq srv cInfo (Just e2eSndParams) subMode
|
||||
@@ -1259,7 +1274,7 @@ joinConnSrvAsync c userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSuppo
|
||||
joinConnSrvAsync _c _userId _connId _enableNtfs (CRContactUri _) _cInfo _subMode _pqSupport _srv = do
|
||||
throwE $ CMD PROHIBITED "joinConnSrvAsync"
|
||||
|
||||
createReplyQueue :: AgentClient -> NetworkRequestMode -> ConnData -> SndQueue -> SubscriptionMode -> SMPServerWithAuth -> AM (SMPQueueInfo, Maybe ClientServiceId)
|
||||
createReplyQueue :: AgentClient -> NetworkRequestMode -> ConnData -> SndQueue -> SubscriptionMode -> SMPServerWithAuth -> AM SMPQueueInfo
|
||||
createReplyQueue c nm ConnData {userId, connId, enableNtfs} SndQueue {smpClientVersion} subMode srv = do
|
||||
ntfServer_ <- if enableNtfs then newQueueNtfServer else pure Nothing
|
||||
(rq, qUri, tSess, sessId) <- newRcvQueue c nm userId connId srv (versionToRange smpClientVersion) SCMInvitation (isJust ntfServer_) subMode
|
||||
@@ -1268,7 +1283,7 @@ createReplyQueue c nm ConnData {userId, connId, enableNtfs} SndQueue {smpClientV
|
||||
rq' <- withStore c $ \db -> upgradeSndConnToDuplex db connId rq subMode
|
||||
lift . when (subMode == SMSubscribe) $ addNewQueueSubscription c rq' tSess sessId
|
||||
mapM_ (newQueueNtfSubscription c rq') ntfServer_
|
||||
pure (qInfo, clientServiceId rq')
|
||||
pure qInfo
|
||||
|
||||
-- | Approve confirmation (LET command) in Reader monad
|
||||
allowConnection' :: AgentClient -> ConnId -> ConfirmationId -> ConnInfo -> AM ()
|
||||
@@ -1281,7 +1296,7 @@ allowConnection' c connId confId ownConnInfo = withConnLock c connId "allowConne
|
||||
_ -> throwE $ CMD PROHIBITED "allowConnection"
|
||||
|
||||
-- | Accept contact (ACPT command) in Reader monad
|
||||
acceptContact' :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> InvitationId -> ConnInfo -> PQSupport -> SubscriptionMode -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
acceptContact' :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> InvitationId -> ConnInfo -> PQSupport -> SubscriptionMode -> AM SndQueueSecured
|
||||
acceptContact' c nm userId connId enableNtfs invId ownConnInfo pqSupport subMode = withConnLock c connId "acceptContact" $ do
|
||||
Invitation {connReq} <- withStore c $ \db -> getInvitation db "acceptContact'" invId
|
||||
r <- joinConn c nm userId connId enableNtfs connReq ownConnInfo pqSupport subMode
|
||||
@@ -1316,7 +1331,7 @@ databaseDiff passed known =
|
||||
in DatabaseDiff {missingIds, extraIds}
|
||||
|
||||
-- | Subscribe to receive connection messages (SUB command) in Reader monad
|
||||
subscribeConnection' :: AgentClient -> ConnId -> AM (Maybe ClientServiceId)
|
||||
subscribeConnection' :: AgentClient -> ConnId -> AM ()
|
||||
subscribeConnection' c connId = toConnResult connId =<< subscribeConnections' c [connId]
|
||||
{-# INLINE subscribeConnection' #-}
|
||||
|
||||
@@ -1332,12 +1347,13 @@ type QDelResult = QCmdResult ()
|
||||
|
||||
type QSubResult = QCmdResult (Maybe SMP.ServiceId)
|
||||
|
||||
subscribeConnections' :: AgentClient -> [ConnId] -> AM (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
|
||||
subscribeConnections' :: AgentClient -> [ConnId] -> AM (Map ConnId (Either AgentErrorType ()))
|
||||
subscribeConnections' _ [] = pure M.empty
|
||||
subscribeConnections' c connIds = subscribeConnections_ c . zip connIds =<< withStore' c (`getConnSubs` connIds)
|
||||
|
||||
subscribeConnections_ :: AgentClient -> [(ConnId, Either StoreError SomeConnSub)] -> AM (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
|
||||
subscribeConnections_ :: AgentClient -> [(ConnId, Either StoreError SomeConnSub)] -> AM (Map ConnId (Either AgentErrorType ()))
|
||||
subscribeConnections_ c conns = do
|
||||
-- TODO [certs rcv] - it should exclude connections already associated, and then if some don't deliver any response they may be unassociated
|
||||
let (subRs, cs) = foldr partitionResultsConns ([], []) conns
|
||||
resumeDelivery cs
|
||||
resumeConnCmds c $ map fst cs
|
||||
@@ -1351,8 +1367,8 @@ subscribeConnections_ c conns = do
|
||||
pure rs
|
||||
where
|
||||
partitionResultsConns :: (ConnId, Either StoreError SomeConnSub) ->
|
||||
(Map ConnId (Either AgentErrorType (Maybe ClientServiceId)), [(ConnId, SomeConnSub)]) ->
|
||||
(Map ConnId (Either AgentErrorType (Maybe ClientServiceId)), [(ConnId, SomeConnSub)])
|
||||
(Map ConnId (Either AgentErrorType ()), [(ConnId, SomeConnSub)]) ->
|
||||
(Map ConnId (Either AgentErrorType ()), [(ConnId, SomeConnSub)])
|
||||
partitionResultsConns (connId, conn_) (rs, cs) = case conn_ of
|
||||
Left e -> (M.insert connId (Left $ storeError e) rs, cs)
|
||||
Right c'@(SomeConn _ conn) -> case conn of
|
||||
@@ -1360,12 +1376,12 @@ subscribeConnections_ c conns = do
|
||||
SndConnection _ sq -> (M.insert connId (sndSubResult sq) rs, cs')
|
||||
RcvConnection _ _ -> (rs, cs')
|
||||
ContactConnection _ _ -> (rs, cs')
|
||||
NewConnection _ -> (M.insert connId (Right Nothing) rs, cs')
|
||||
NewConnection _ -> (M.insert connId (Right ()) rs, cs')
|
||||
where
|
||||
cs' = (connId, c') : cs
|
||||
sndSubResult :: SndQueue -> Either AgentErrorType (Maybe ClientServiceId)
|
||||
sndSubResult :: SndQueue -> Either AgentErrorType ()
|
||||
sndSubResult SndQueue {status} = case status of
|
||||
Confirmed -> Right Nothing
|
||||
Confirmed -> Right ()
|
||||
Active -> Left $ CONN SIMPLEX "subscribeConnections"
|
||||
_ -> Left $ INTERNAL "unexpected queue status"
|
||||
rcvQueues :: (ConnId, SomeConnSub) -> [RcvQueueSub]
|
||||
@@ -1386,9 +1402,9 @@ subscribeConnections_ c conns = do
|
||||
order (_, Right _) = 3
|
||||
order _ = 4
|
||||
-- TODO [certs rcv] store associations of queues with client service ID
|
||||
storeClientServiceAssocs :: Map ConnId (Either AgentErrorType (Maybe SMP.ServiceId)) -> AM (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
|
||||
storeClientServiceAssocs = pure . M.map (Nothing <$)
|
||||
sendNtfCreate :: NtfSupervisor -> Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> [(ConnId, SomeConnSub)] -> AM' ()
|
||||
storeClientServiceAssocs :: Map ConnId (Either AgentErrorType (Maybe SMP.ServiceId)) -> AM (Map ConnId (Either AgentErrorType ()))
|
||||
storeClientServiceAssocs = pure . M.map (() <$)
|
||||
sendNtfCreate :: NtfSupervisor -> Map ConnId (Either AgentErrorType ()) -> [(ConnId, SomeConnSub)] -> AM' ()
|
||||
sendNtfCreate ns rcvRs cs = do
|
||||
let oks = M.keysSet $ M.filter (either temporaryAgentError $ const True) rcvRs
|
||||
(csCreate, csDelete) = foldr (groupConnIds oks) ([], []) cs
|
||||
@@ -1412,7 +1428,7 @@ subscribeConnections_ c conns = do
|
||||
DuplexConnection _ _ sqs -> L.toList sqs
|
||||
SndConnection _ sq -> [sq]
|
||||
_ -> []
|
||||
notifyResultError :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> AM ()
|
||||
notifyResultError :: Map ConnId (Either AgentErrorType ()) -> AM ()
|
||||
notifyResultError rs = do
|
||||
let actual = M.size rs
|
||||
expected = length conns
|
||||
@@ -1472,15 +1488,15 @@ subscribeAllConnections' c onlyNeeded activeUserId_ = handleErr $ do
|
||||
sqs <- withStore' c getAllSndQueuesForDelivery
|
||||
lift $ mapM_ (resumeMsgDelivery c) sqs
|
||||
|
||||
resubscribeConnection' :: AgentClient -> ConnId -> AM (Maybe ClientServiceId)
|
||||
resubscribeConnection' :: AgentClient -> ConnId -> AM ()
|
||||
resubscribeConnection' c connId = toConnResult connId =<< resubscribeConnections' c [connId]
|
||||
{-# INLINE resubscribeConnection' #-}
|
||||
|
||||
resubscribeConnections' :: AgentClient -> [ConnId] -> AM (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
|
||||
resubscribeConnections' :: AgentClient -> [ConnId] -> AM (Map ConnId (Either AgentErrorType ()))
|
||||
resubscribeConnections' _ [] = pure M.empty
|
||||
resubscribeConnections' c connIds = do
|
||||
conns <- zip connIds <$> withStore' c (`getConnSubs` connIds)
|
||||
let r = M.fromList $ map (,Right Nothing) connIds -- TODO [certs rcv]
|
||||
let r = M.fromList $ map (,Right ()) connIds
|
||||
conns' <- filterM (fmap not . isActiveConn . snd) conns
|
||||
-- union is left-biased, so results returned by subscribeConnections' take precedence
|
||||
(`M.union` r) <$> subscribeConnections_ c conns'
|
||||
@@ -1491,9 +1507,15 @@ resubscribeConnections' c connIds = do
|
||||
[] -> pure True
|
||||
rqs' -> anyM $ map (atomically . hasActiveSubscription c) rqs'
|
||||
|
||||
-- TODO [certs rcv]
|
||||
subscribeClientService' :: AgentClient -> ClientServiceId -> AM Int
|
||||
subscribeClientService' = undefined
|
||||
-- TODO [certs rcv] compare hash with lock
|
||||
subscribeClientServices' :: AgentClient -> UserId -> AM (Map SMPServer (Either AgentErrorType (Int64, IdsHash)))
|
||||
subscribeClientServices' c userId =
|
||||
ifM useService subscribe $ throwError $ CMD PROHIBITED "no user service allowed"
|
||||
where
|
||||
useService = liftIO $ (Just True ==) <$> TM.lookupIO userId (useClientServices c)
|
||||
subscribe = do
|
||||
srvs <- withStore' c (`getClientServiceServers` userId)
|
||||
lift $ M.fromList . zip srvs <$> mapConcurrently (tryAllErrors' . subscribeClientService c userId) srvs
|
||||
|
||||
-- requesting messages sequentially, to reduce memory usage
|
||||
getConnectionMessages' :: AgentClient -> NonEmpty ConnMsgReq -> AM' (NonEmpty (Either AgentErrorType (Maybe SMPMsgMeta)))
|
||||
@@ -1655,13 +1677,13 @@ runCommandProcessing c@AgentClient {subQ} connId server_ Worker {doWork} = do
|
||||
NEW enableNtfs (ACM cMode) pqEnc subMode -> noServer $ do
|
||||
triedHosts <- newTVarIO S.empty
|
||||
tryCommand . withNextSrv c userId storageSrvs triedHosts [] $ \srv -> do
|
||||
(CCLink cReq _, service) <- newRcvConnSrv c NRMBackground userId connId enableNtfs cMode Nothing Nothing pqEnc subMode srv
|
||||
notify $ INV (ACR cMode cReq) service
|
||||
CCLink cReq _ <- newRcvConnSrv c NRMBackground userId connId enableNtfs cMode Nothing Nothing pqEnc subMode srv
|
||||
notify $ INV (ACR cMode cReq)
|
||||
JOIN enableNtfs (ACR _ cReq@(CRInvitationUri ConnReqUriData {crSmpQueues = q :| _} _)) pqEnc subMode connInfo -> noServer $ do
|
||||
triedHosts <- newTVarIO S.empty
|
||||
tryCommand . withNextSrv c userId storageSrvs triedHosts [qServer q] $ \srv -> do
|
||||
(sqSecured, service) <- joinConnSrvAsync c userId connId enableNtfs cReq connInfo pqEnc subMode srv
|
||||
notify $ JOINED sqSecured service
|
||||
sqSecured <- joinConnSrvAsync c userId connId enableNtfs cReq connInfo pqEnc subMode srv
|
||||
notify $ JOINED sqSecured
|
||||
LET confId ownCInfo -> withServer' . tryCommand $ allowConnection' c connId confId ownCInfo >> notify OK
|
||||
ACK msgId rcptInfo_ -> withServer' . tryCommand $ ackMessage' c connId msgId rcptInfo_ >> notify OK
|
||||
SWCH ->
|
||||
@@ -2818,7 +2840,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
|
||||
SMP.SUB -> case respOrErr of
|
||||
Right SMP.OK -> liftIO $ processSubOk rq upConnIds
|
||||
-- TODO [certs rcv] associate queue with the service
|
||||
Right (SMP.SOK serviceId_) -> liftIO $ processSubOk rq upConnIds
|
||||
Right (SMP.SOK _serviceId_) -> liftIO $ processSubOk rq upConnIds
|
||||
Right msg@SMP.MSG {} -> do
|
||||
liftIO $ processSubOk rq upConnIds -- the connection is UP even when processing this particular message fails
|
||||
runProcessSMP rq conn (toConnData conn) msg
|
||||
@@ -3053,7 +3075,9 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
|
||||
notifyEnd removed
|
||||
| removed = notify END >> logServer "<--" c srv rId "END"
|
||||
| otherwise = logServer "<--" c srv rId "END from disconnected client - ignored"
|
||||
-- Possibly, we need to add some flag to connection that it was deleted
|
||||
-- TODO [certs rcv]
|
||||
r@(SMP.ENDS _) -> unexpected r
|
||||
-- TODO [certs rcv] Possibly, we need to add some flag to connection that it was deleted
|
||||
SMP.DELD -> atomically (removeSubscription c tSess connId rq) >> notify DELD
|
||||
SMP.ERR e -> notify $ ERR $ SMP (B.unpack $ strEncode srv) e
|
||||
r -> unexpected r
|
||||
@@ -3439,22 +3463,22 @@ connectReplyQueues c cData@ConnData {userId, connId} ownConnInfo sq_ (qInfo :| _
|
||||
(sq, _) <- lift $ newSndQueue userId connId qInfo' Nothing
|
||||
withStore c $ \db -> upgradeRcvConnToDuplex db connId sq
|
||||
|
||||
secureConfirmQueueAsync :: AgentClient -> ConnData -> Maybe RcvQueue -> SndQueue -> SMPServerWithAuth -> ConnInfo -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> SubscriptionMode -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
secureConfirmQueueAsync :: AgentClient -> ConnData -> Maybe RcvQueue -> SndQueue -> SMPServerWithAuth -> ConnInfo -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> SubscriptionMode -> AM SndQueueSecured
|
||||
secureConfirmQueueAsync c cData rq_ sq srv connInfo e2eEncryption_ subMode = do
|
||||
sqSecured <- agentSecureSndQueue c NRMBackground cData sq
|
||||
(qInfo, service) <- mkAgentConfirmation c NRMBackground cData rq_ sq srv connInfo subMode
|
||||
qInfo <- mkAgentConfirmation c NRMBackground cData rq_ sq srv connInfo subMode
|
||||
storeConfirmation c cData sq e2eEncryption_ qInfo
|
||||
lift $ submitPendingMsg c sq
|
||||
pure (sqSecured, service)
|
||||
pure sqSecured
|
||||
|
||||
secureConfirmQueue :: AgentClient -> NetworkRequestMode -> ConnData -> Maybe RcvQueue -> SndQueue -> SMPServerWithAuth -> ConnInfo -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> SubscriptionMode -> AM (SndQueueSecured, Maybe ClientServiceId)
|
||||
secureConfirmQueue :: AgentClient -> NetworkRequestMode -> ConnData -> Maybe RcvQueue -> SndQueue -> SMPServerWithAuth -> ConnInfo -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> SubscriptionMode -> AM SndQueueSecured
|
||||
secureConfirmQueue c nm cData@ConnData {connId, connAgentVersion, pqSupport} rq_ sq srv connInfo e2eEncryption_ subMode = do
|
||||
sqSecured <- agentSecureSndQueue c nm cData sq
|
||||
(qInfo, service) <- mkAgentConfirmation c nm cData rq_ sq srv connInfo subMode
|
||||
qInfo <- mkAgentConfirmation c nm cData rq_ sq srv connInfo subMode
|
||||
msg <- mkConfirmation qInfo
|
||||
void $ sendConfirmation c nm sq msg
|
||||
withStore' c $ \db -> setSndQueueStatus db sq Confirmed
|
||||
pure (sqSecured, service)
|
||||
pure sqSecured
|
||||
where
|
||||
mkConfirmation :: AgentMessage -> AM MsgBody
|
||||
mkConfirmation aMessage = do
|
||||
@@ -3480,12 +3504,12 @@ agentSecureSndQueue c nm ConnData {connAgentVersion} sq@SndQueue {queueMode, sta
|
||||
sndSecure = senderCanSecure queueMode
|
||||
initiatorRatchetOnConf = connAgentVersion >= ratchetOnConfSMPAgentVersion
|
||||
|
||||
mkAgentConfirmation :: AgentClient -> NetworkRequestMode -> ConnData -> Maybe RcvQueue -> SndQueue -> SMPServerWithAuth -> ConnInfo -> SubscriptionMode -> AM (AgentMessage, Maybe ClientServiceId)
|
||||
mkAgentConfirmation :: AgentClient -> NetworkRequestMode -> ConnData -> Maybe RcvQueue -> SndQueue -> SMPServerWithAuth -> ConnInfo -> SubscriptionMode -> AM AgentMessage
|
||||
mkAgentConfirmation c nm cData rq_ sq srv connInfo subMode = do
|
||||
(qInfo, service) <- case rq_ of
|
||||
qInfo <- case rq_ of
|
||||
Nothing -> createReplyQueue c nm cData sq subMode srv
|
||||
Just rq@RcvQueue {smpClientVersion = v, clientService} -> pure (SMPQueueInfo v $ rcvSMPQueueAddress rq, dbServiceId <$> clientService)
|
||||
pure (AgentConnInfoReply (qInfo :| []) connInfo, service)
|
||||
Just rq@RcvQueue {smpClientVersion = v} -> pure $ SMPQueueInfo v $ rcvSMPQueueAddress rq
|
||||
pure $ AgentConnInfoReply (qInfo :| []) connInfo
|
||||
|
||||
enqueueConfirmation :: AgentClient -> ConnData -> SndQueue -> ConnInfo -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> AM ()
|
||||
enqueueConfirmation c cData sq connInfo e2eEncryption_ = do
|
||||
|
||||
@@ -49,6 +49,7 @@ module Simplex.Messaging.Agent.Client
|
||||
newRcvQueue_,
|
||||
subscribeQueues,
|
||||
subscribeUserServerQueues,
|
||||
subscribeClientService,
|
||||
processClientNotices,
|
||||
getQueueMessage,
|
||||
decryptSMPMessage,
|
||||
@@ -223,6 +224,7 @@ import Data.Text.Encoding
|
||||
import Data.Time (UTCTime, addUTCTime, defaultTimeLocale, formatTime, getCurrentTime)
|
||||
import Data.Time.Clock.System (getSystemTime)
|
||||
import Data.Word (Word16)
|
||||
import qualified Data.X509.Validation as XV
|
||||
import Network.Socket (HostName)
|
||||
import Simplex.FileTransfer.Client (XFTPChunkSpec (..), XFTPClient, XFTPClientConfig (..), XFTPClientError)
|
||||
import qualified Simplex.FileTransfer.Client as X
|
||||
@@ -238,7 +240,7 @@ import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.RetryInterval
|
||||
import Simplex.Messaging.Agent.Stats
|
||||
import Simplex.Messaging.Agent.Store
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (getClientNotices, updateClientNotices)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore
|
||||
import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Agent.Store.Entity
|
||||
@@ -262,6 +264,7 @@ import Simplex.Messaging.Protocol
|
||||
NetworkError (..),
|
||||
MsgFlags (..),
|
||||
MsgId,
|
||||
IdsHash,
|
||||
NtfServer,
|
||||
NtfServerWithAuth,
|
||||
ProtoServer,
|
||||
@@ -296,8 +299,9 @@ import Simplex.Messaging.Session
|
||||
import Simplex.Messaging.SystemTime
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Transport (SMPVersion, SessionId, THandleParams (sessionId, thVersion), TransportError (..), TransportPeer (..), sndAuthKeySMPVersion, shortLinksSMPVersion, newNtfCredsSMPVersion)
|
||||
import Simplex.Messaging.Transport (SMPServiceRole (..), SMPVersion, ServiceCredentials (..), SessionId, THClientService' (..), THandleParams (sessionId, thVersion), TransportError (..), TransportPeer (..), sndAuthKeySMPVersion, shortLinksSMPVersion, newNtfCredsSMPVersion)
|
||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||
import Simplex.Messaging.Transport.Credentials
|
||||
import Simplex.Messaging.Util
|
||||
import Simplex.Messaging.Version
|
||||
import System.Mem.Weak (Weak, deRefWeak)
|
||||
@@ -331,6 +335,7 @@ data AgentClient = AgentClient
|
||||
msgQ :: TBQueue (ServerTransmissionBatch SMPVersion ErrorType BrokerMsg),
|
||||
smpServers :: TMap UserId (UserServers 'PSMP),
|
||||
smpClients :: TMap SMPTransportSession SMPClientVar,
|
||||
useClientServices :: TMap UserId Bool,
|
||||
-- smpProxiedRelays:
|
||||
-- SMPTransportSession defines connection from proxy to relay,
|
||||
-- SMPServerWithAuth defines client connected to SMP proxy (with the same userId and entityId in TransportSession)
|
||||
@@ -495,7 +500,7 @@ data UserNetworkType = UNNone | UNCellular | UNWifi | UNEthernet | UNOther
|
||||
|
||||
-- | Creates an SMP agent client instance that receives commands and sends responses via 'TBQueue's.
|
||||
newAgentClient :: Int -> InitialAgentServers -> UTCTime -> Map (Maybe SMPServer) (Maybe SystemSeconds) -> Env -> IO AgentClient
|
||||
newAgentClient clientId InitialAgentServers {smp, ntf, xftp, netCfg, presetDomains, presetServers} currentTs notices agentEnv = do
|
||||
newAgentClient clientId InitialAgentServers {smp, ntf, xftp, netCfg, useServices, presetDomains, presetServers} currentTs notices agentEnv = do
|
||||
let cfg = config agentEnv
|
||||
qSize = tbqSize cfg
|
||||
proxySessTs <- newTVarIO =<< getCurrentTime
|
||||
@@ -505,6 +510,7 @@ newAgentClient clientId InitialAgentServers {smp, ntf, xftp, netCfg, presetDomai
|
||||
msgQ <- newTBQueueIO qSize
|
||||
smpServers <- newTVarIO $ M.map mkUserServers smp
|
||||
smpClients <- TM.emptyIO
|
||||
useClientServices <- newTVarIO useServices
|
||||
smpProxiedRelays <- TM.emptyIO
|
||||
ntfServers <- newTVarIO ntf
|
||||
ntfClients <- TM.emptyIO
|
||||
@@ -544,6 +550,7 @@ newAgentClient clientId InitialAgentServers {smp, ntf, xftp, netCfg, presetDomai
|
||||
msgQ,
|
||||
smpServers,
|
||||
smpClients,
|
||||
useClientServices,
|
||||
smpProxiedRelays,
|
||||
ntfServers,
|
||||
ntfClients,
|
||||
@@ -598,6 +605,28 @@ agentDRG :: AgentClient -> TVar ChaChaDRG
|
||||
agentDRG AgentClient {agentEnv = Env {random}} = random
|
||||
{-# INLINE agentDRG #-}
|
||||
|
||||
getServiceCredentials :: AgentClient -> UserId -> SMPServer -> AM (Maybe (ServiceCredentials, Maybe ServiceId))
|
||||
getServiceCredentials c userId srv =
|
||||
liftIO (TM.lookupIO userId $ useClientServices c)
|
||||
$>>= \useService -> if useService then Just <$> getService else pure Nothing
|
||||
where
|
||||
getService :: AM (ServiceCredentials, Maybe ServiceId)
|
||||
getService = do
|
||||
let g = agentDRG c
|
||||
((C.KeyHash kh, serviceCreds), serviceId_) <-
|
||||
withStore' c $ \db ->
|
||||
getClientService db userId srv >>= \case
|
||||
Just service -> pure service
|
||||
Nothing -> do
|
||||
cred <- genCredentials g Nothing (25, 24 * 999999) "simplex"
|
||||
let tlsCreds = tlsCredentials [cred]
|
||||
createClientService db userId srv tlsCreds
|
||||
pure (tlsCreds, Nothing)
|
||||
(_, pk) <- atomically $ C.generateKeyPair g
|
||||
let serviceSignKey = C.APrivateSignKey C.SEd25519 pk
|
||||
creds = ServiceCredentials {serviceRole = SRMessaging, serviceCreds, serviceCertHash = XV.Fingerprint kh, serviceSignKey}
|
||||
pure (creds, serviceId_)
|
||||
|
||||
class (Encoding err, Show err) => ProtocolServerClient v err msg | msg -> v, msg -> err where
|
||||
type Client msg = c | c -> msg
|
||||
getProtocolServerClient :: AgentClient -> NetworkRequestMode -> TransportSession msg -> AM (Client msg)
|
||||
@@ -701,7 +730,7 @@ getSMPProxyClient c@AgentClient {active, smpClients, smpProxiedRelays, workerSeq
|
||||
Nothing -> Left $ BROKER (B.unpack $ strEncode srv) TIMEOUT
|
||||
|
||||
smpConnectClient :: AgentClient -> NetworkRequestMode -> SMPTransportSession -> TMap SMPServer ProxiedRelayVar -> SMPClientVar -> AM SMPConnectedClient
|
||||
smpConnectClient c@AgentClient {smpClients, msgQ, proxySessTs, presetDomains} nm tSess@(_, srv, _) prs v =
|
||||
smpConnectClient c@AgentClient {smpClients, msgQ, proxySessTs, presetDomains} nm tSess@(userId, srv, _) prs v =
|
||||
newProtocolClient c tSess smpClients connectClient v
|
||||
`catchAllErrors` \e -> lift (resubscribeSMPSession c tSess) >> throwE e
|
||||
where
|
||||
@@ -709,12 +738,22 @@ smpConnectClient c@AgentClient {smpClients, msgQ, proxySessTs, presetDomains} nm
|
||||
connectClient v' = do
|
||||
cfg <- lift $ getClientConfig c smpCfg
|
||||
g <- asks random
|
||||
service <- getServiceCredentials c userId srv
|
||||
let cfg' = cfg {serviceCredentials = fst <$> service}
|
||||
env <- ask
|
||||
liftError (protocolClientError SMP $ B.unpack $ strEncode srv) $ do
|
||||
smp <- liftError (protocolClientError SMP $ B.unpack $ strEncode srv) $ do
|
||||
ts <- readTVarIO proxySessTs
|
||||
smp <- ExceptT $ getProtocolClient g nm tSess cfg presetDomains (Just msgQ) ts $ smpClientDisconnected c tSess env v' prs
|
||||
atomically $ SS.setSessionId tSess (sessionId $ thParams smp) $ currentSubs c
|
||||
pure SMPConnectedClient {connectedClient = smp, proxiedRelays = prs}
|
||||
ExceptT $ getProtocolClient g nm tSess cfg' presetDomains (Just msgQ) ts $ smpClientDisconnected c tSess env v' prs
|
||||
atomically $ SS.setSessionId tSess (sessionId $ thParams smp) $ currentSubs c
|
||||
updateClientService service smp
|
||||
pure SMPConnectedClient {connectedClient = smp, proxiedRelays = prs}
|
||||
updateClientService service smp = case (service, smpClientService smp) of
|
||||
(Just (_, serviceId_), Just THClientService {serviceId})
|
||||
| serviceId_ /= Just serviceId -> withStore' c $ \db -> setClientServiceId db userId srv serviceId
|
||||
| otherwise -> pure ()
|
||||
(Just _, Nothing) -> withStore' c $ \db -> deleteClientService db userId srv -- e.g., server version downgrade
|
||||
(Nothing, Just _) -> logError "server returned serviceId without service credentials in request"
|
||||
(Nothing, Nothing) -> pure ()
|
||||
|
||||
smpClientDisconnected :: AgentClient -> SMPTransportSession -> Env -> SMPClientVar -> TMap SMPServer ProxiedRelayVar -> SMPClient -> IO ()
|
||||
smpClientDisconnected c@AgentClient {active, smpClients, smpProxiedRelays} tSess@(userId, srv, cId) env v prs client = do
|
||||
@@ -862,7 +901,6 @@ waitForProtocolClient c nm tSess@(_, srv, _) clients v = do
|
||||
(throwE e)
|
||||
Nothing -> throwE $ BROKER (B.unpack $ strEncode srv) TIMEOUT
|
||||
|
||||
-- clientConnected arg is only passed for SMP server
|
||||
newProtocolClient ::
|
||||
forall v err msg.
|
||||
(ProtocolTypeI (ProtoType msg), ProtocolServerClient v err msg) =>
|
||||
@@ -1399,7 +1437,8 @@ newRcvQueue_ c nm userId connId (ProtoServerWithAuth srv auth) vRange cqrd enabl
|
||||
withClient c nm tSess $ \(SMPConnectedClient smp _) -> do
|
||||
(ntfKeys, ntfCreds) <- liftIO $ mkNtfCreds a g smp
|
||||
(thParams smp,ntfKeys,) <$> createSMPQueue smp nm nonce_ rKeys dhKey auth subMode (queueReqData cqrd) ntfCreds
|
||||
-- TODO [certs rcv] validate that serviceId is the same as in the client session
|
||||
-- TODO [certs rcv] validate that serviceId is the same as in the client session, fail otherwise
|
||||
-- possibly, it should allow returning Nothing - it would indicate incorrect old version
|
||||
liftIO . logServer "<--" c srv NoEntity $ B.unwords ["IDS", logSecret rcvId, logSecret sndId]
|
||||
shortLink <- mkShortLinkCreds thParams' qik
|
||||
let rq =
|
||||
@@ -1415,7 +1454,7 @@ newRcvQueue_ c nm userId connId (ProtoServerWithAuth srv auth) vRange cqrd enabl
|
||||
sndId,
|
||||
queueMode,
|
||||
shortLink,
|
||||
clientService = ClientService DBNewEntity <$> serviceId,
|
||||
rcvServiceAssoc = isJust serviceId,
|
||||
status = New,
|
||||
enableNtfs,
|
||||
clientNoticeId = Nothing,
|
||||
@@ -1650,6 +1689,11 @@ processClientNotices c@AgentClient {presetServers} tSess notices = do
|
||||
logError $ "processClientNotices error: " <> tshow e
|
||||
notifySub' c "" $ ERR e
|
||||
|
||||
subscribeClientService :: AgentClient -> UserId -> SMPServer -> AM (Int64, IdsHash)
|
||||
subscribeClientService c userId srv =
|
||||
withLogClient c NRMBackground (userId, srv, Nothing) B.empty "SUBS" $
|
||||
(`subscribeService` SMP.SRecipientService) . connectedClient
|
||||
|
||||
activeClientSession :: AgentClient -> SMPTransportSession -> SessionId -> STM Bool
|
||||
activeClientSession c tSess sessId = sameSess <$> tryReadSessVar tSess (smpClients c)
|
||||
where
|
||||
|
||||
@@ -90,6 +90,7 @@ data InitialAgentServers = InitialAgentServers
|
||||
ntf :: [NtfServer],
|
||||
xftp :: Map UserId (NonEmpty (ServerCfg 'PXFTP)),
|
||||
netCfg :: NetworkConfig,
|
||||
useServices :: Map UserId Bool,
|
||||
presetDomains :: [HostName],
|
||||
presetServers :: [SMPServer]
|
||||
}
|
||||
|
||||
@@ -126,9 +126,6 @@ module Simplex.Messaging.Agent.Protocol
|
||||
ContactConnType (..),
|
||||
ShortLinkScheme (..),
|
||||
LinkKey (..),
|
||||
StoredClientService (..),
|
||||
ClientService,
|
||||
ClientServiceId,
|
||||
sameConnReqContact,
|
||||
sameShortLinkContact,
|
||||
simplexChat,
|
||||
@@ -212,7 +209,6 @@ import Simplex.FileTransfer.Transport (XFTPErrorType)
|
||||
import Simplex.FileTransfer.Types (FileErrorType)
|
||||
import Simplex.Messaging.Agent.QueryString
|
||||
import Simplex.Messaging.Agent.Store.DB (Binary (..), FromField (..), ToField (..), blobFieldDecoder, fromTextField_)
|
||||
import Simplex.Messaging.Agent.Store.Entity
|
||||
import Simplex.Messaging.Client (ProxyClientError)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet
|
||||
@@ -381,7 +377,7 @@ type SndQueueSecured = Bool
|
||||
|
||||
-- | Parameterized type for SMP agent events
|
||||
data AEvent (e :: AEntity) where
|
||||
INV :: AConnectionRequestUri -> Maybe ClientServiceId -> AEvent AEConn
|
||||
INV :: AConnectionRequestUri -> AEvent AEConn
|
||||
CONF :: ConfirmationId -> PQSupport -> [SMPServer] -> ConnInfo -> AEvent AEConn -- ConnInfo is from sender, [SMPServer] will be empty only in v1 handshake
|
||||
REQ :: InvitationId -> PQSupport -> NonEmpty SMPServer -> ConnInfo -> AEvent AEConn -- ConnInfo is from sender
|
||||
INFO :: PQSupport -> ConnInfo -> AEvent AEConn
|
||||
@@ -407,7 +403,7 @@ data AEvent (e :: AEntity) where
|
||||
DEL_USER :: Int64 -> AEvent AENone
|
||||
STAT :: ConnectionStats -> AEvent AEConn
|
||||
OK :: AEvent AEConn
|
||||
JOINED :: SndQueueSecured -> Maybe ClientServiceId -> AEvent AEConn
|
||||
JOINED :: SndQueueSecured -> AEvent AEConn
|
||||
ERR :: AgentErrorType -> AEvent AEConn
|
||||
ERRS :: NonEmpty (ConnId, AgentErrorType) -> AEvent AENone
|
||||
SUSPENDED :: AEvent AENone
|
||||
@@ -1783,16 +1779,6 @@ instance Encoding UserLinkData where
|
||||
smpP = UserLinkData <$> ((A.char '\255' *> (unLarge <$> smpP)) <|> smpP)
|
||||
{-# INLINE smpP #-}
|
||||
|
||||
data StoredClientService (s :: DBStored) = ClientService
|
||||
{ dbServiceId :: DBEntityId' s,
|
||||
serviceId :: SMP.ServiceId
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
type ClientService = StoredClientService 'DBStored
|
||||
|
||||
type ClientServiceId = DBEntityId
|
||||
|
||||
-- | SMP queue status.
|
||||
data QueueStatus
|
||||
= -- | queue is created
|
||||
|
||||
@@ -85,7 +85,7 @@ data StoredRcvQueue (q :: DBStored) = RcvQueue
|
||||
-- | short link ID and credentials
|
||||
shortLink :: Maybe ShortLinkCreds,
|
||||
-- | associated client service
|
||||
clientService :: Maybe (StoredClientService q),
|
||||
rcvServiceAssoc :: ServiceAssoc,
|
||||
-- | queue status
|
||||
status :: QueueStatus,
|
||||
-- | to enable notifications for this queue - this field is duplicated from ConnData
|
||||
@@ -134,9 +134,7 @@ data ShortLinkCreds = ShortLinkCreds
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
clientServiceId :: RcvQueue -> Maybe ClientServiceId
|
||||
clientServiceId = fmap dbServiceId . clientService
|
||||
{-# INLINE clientServiceId #-}
|
||||
type ServiceAssoc = Bool
|
||||
|
||||
rcvSMPQueueAddress :: RcvQueue -> SMPQueueAddress
|
||||
rcvSMPQueueAddress RcvQueue {server, sndId, e2ePrivKey, queueMode} =
|
||||
|
||||
@@ -35,6 +35,14 @@ module Simplex.Messaging.Agent.Store.AgentStore
|
||||
deleteUsersWithoutConns,
|
||||
checkUser,
|
||||
|
||||
-- * Client services
|
||||
createClientService,
|
||||
getClientService,
|
||||
getClientServiceServers,
|
||||
setClientServiceId,
|
||||
deleteClientService,
|
||||
deleteClientServices,
|
||||
|
||||
-- * Queues and connections
|
||||
createNewConn,
|
||||
updateNewConnRcv,
|
||||
@@ -274,7 +282,9 @@ import qualified Data.Set as S
|
||||
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
|
||||
import Data.Time.Clock (NominalDiffTime, UTCTime, addUTCTime, getCurrentTime)
|
||||
import Data.Word (Word32)
|
||||
import qualified Data.X509 as X
|
||||
import Network.Socket (ServiceName)
|
||||
import qualified Network.TLS as TLS
|
||||
import Simplex.FileTransfer.Client (XFTPChunkSpec (..))
|
||||
import Simplex.FileTransfer.Description
|
||||
import Simplex.FileTransfer.Protocol (FileParty (..), SFileParty (..))
|
||||
@@ -390,6 +400,75 @@ deleteUsersWithoutConns db = do
|
||||
forM_ userIds $ DB.execute db "DELETE FROM users WHERE user_id = ?" . Only
|
||||
pure userIds
|
||||
|
||||
createClientService :: DB.Connection -> UserId -> SMPServer -> (C.KeyHash, TLS.Credential) -> IO ()
|
||||
createClientService db userId srv (kh, (cert, pk)) =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO client_services
|
||||
(user_id, host, port, service_cert_hash, service_cert, service_priv_key)
|
||||
VALUES (?,?,?,?,?,?)
|
||||
ON CONFLICT (user_id, host, port)
|
||||
DO UPDATE SET
|
||||
service_cert_hash = EXCLUDED.service_cert_hash,
|
||||
service_cert = EXCLUDED.service_cert,
|
||||
service_priv_key = EXCLUDED.service_priv_key,
|
||||
rcv_service_id = NULL
|
||||
|]
|
||||
(userId, host srv, port srv, kh, cert, pk)
|
||||
|
||||
getClientService :: DB.Connection -> UserId -> SMPServer -> IO (Maybe ((C.KeyHash, TLS.Credential), Maybe ServiceId))
|
||||
getClientService db userId srv =
|
||||
maybeFirstRow toService $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT service_cert_hash, service_cert, service_priv_key, rcv_service_id
|
||||
FROM client_services
|
||||
WHERE user_id = ? AND host = ? AND port = ?
|
||||
|]
|
||||
(userId, host srv, port srv)
|
||||
where
|
||||
toService (kh, cert, pk, serviceId_) = ((kh, (cert, pk)), serviceId_)
|
||||
|
||||
getClientServiceServers :: DB.Connection -> UserId -> IO [SMPServer]
|
||||
getClientServiceServers db userId =
|
||||
map toServer
|
||||
<$> DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT c.host, c.port, s.key_hash
|
||||
FROM client_services c
|
||||
JOIN servers s ON s.host = c.host AND s.port = c.port
|
||||
|]
|
||||
(Only userId)
|
||||
where
|
||||
toServer (host, port, kh) = SMPServer host port kh
|
||||
|
||||
setClientServiceId :: DB.Connection -> UserId -> SMPServer -> ServiceId -> IO ()
|
||||
setClientServiceId db userId srv serviceId =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE client_services
|
||||
SET rcv_service_id = ?
|
||||
WHERE user_id = ? AND host = ? AND port = ?
|
||||
|]
|
||||
(serviceId, userId, host srv, port srv)
|
||||
|
||||
deleteClientService :: DB.Connection -> UserId -> SMPServer -> IO ()
|
||||
deleteClientService db userId srv =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
DELETE FROM client_services
|
||||
WHERE user_id = ? AND host = ? AND port = ?
|
||||
|]
|
||||
(userId, host srv, port srv)
|
||||
|
||||
deleteClientServices :: DB.Connection -> UserId -> IO ()
|
||||
deleteClientServices db userId = DB.execute db "DELETE FROM client_services WHERE user_id = ?" (Only userId)
|
||||
|
||||
createConn_ ::
|
||||
TVar ChaChaDRG ->
|
||||
ConnData ->
|
||||
@@ -1926,6 +2005,15 @@ deriving newtype instance ToField ChunkReplicaId
|
||||
|
||||
deriving newtype instance FromField ChunkReplicaId
|
||||
|
||||
instance ToField X.CertificateChain where toField = toField . Binary . smpEncode . C.encodeCertChain
|
||||
|
||||
instance FromField X.CertificateChain where fromField = blobFieldDecoder (parseAll C.certChainP)
|
||||
|
||||
instance ToField X.PrivKey where toField = toField . Binary . C.encodeASNObj
|
||||
|
||||
instance FromField X.PrivKey where
|
||||
fromField = blobFieldDecoder $ C.decodeASNKey >=> \case (pk, []) -> Right pk; r -> C.asnKeyError r
|
||||
|
||||
fromOnlyBI :: Only BoolInt -> Bool
|
||||
fromOnlyBI (Only (BI b)) = b
|
||||
{-# INLINE fromOnlyBI #-}
|
||||
@@ -2005,19 +2093,18 @@ insertRcvQueue_ db connId' rq@RcvQueue {..} subMode serverKeyHash_ = do
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO rcv_queues
|
||||
( host, port, rcv_id, conn_id, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_dh_secret,
|
||||
( host, port, rcv_id, rcv_service_assoc, conn_id, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_dh_secret,
|
||||
snd_id, queue_mode, status, to_subscribe, rcv_queue_id, rcv_primary, replace_rcv_queue_id, smp_client_version, server_key_hash,
|
||||
link_id, link_key, link_priv_sig_key, link_enc_fixed_data,
|
||||
ntf_public_key, ntf_private_key, ntf_id, rcv_ntf_dh_secret
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
|
||||
|]
|
||||
( (host server, port server, rcvId, connId', rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret)
|
||||
( (host server, port server, rcvId, rcvServiceAssoc, connId', rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret)
|
||||
:. (sndId, queueMode, status, BI toSubscribe, qId, BI primary, dbReplaceQueueId, smpClientVersion, serverKeyHash_)
|
||||
:. (shortLinkId <$> shortLink, shortLinkKey <$> shortLink, linkPrivSigKey <$> shortLink, linkEncFixedData <$> shortLink)
|
||||
:. ntfCredsFields
|
||||
)
|
||||
-- TODO [certs rcv] save client service
|
||||
pure (rq :: NewRcvQueue) {connId = connId', dbQueueId = qId, clientService = Nothing}
|
||||
pure (rq :: NewRcvQueue) {connId = connId', dbQueueId = qId}
|
||||
where
|
||||
toSubscribe = subMode == SMOnlyCreate
|
||||
ntfCredsFields = case clientNtfCreds of
|
||||
@@ -2371,7 +2458,7 @@ rcvQueueQuery =
|
||||
[sql|
|
||||
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
|
||||
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, c.enable_ntfs, q.client_notice_id,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
|
||||
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors, q.rcv_service_assoc,
|
||||
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret,
|
||||
q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data
|
||||
FROM rcv_queues q
|
||||
@@ -2381,13 +2468,13 @@ rcvQueueQuery =
|
||||
|
||||
toRcvQueue ::
|
||||
(UserId, C.KeyHash, ConnId, NonEmpty TransportHost, ServiceName, SMP.RecipientId, SMP.RcvPrivateAuthKey, SMP.RcvDhSecret, C.PrivateKeyX25519, Maybe C.DhSecretX25519, SMP.SenderId, Maybe QueueMode)
|
||||
:. (QueueStatus, Maybe BoolInt, Maybe NoticeId, DBEntityId, BoolInt, Maybe Int64, Maybe RcvSwitchStatus, Maybe VersionSMPC, Int)
|
||||
:. (QueueStatus, Maybe BoolInt, Maybe NoticeId, DBEntityId, BoolInt, Maybe Int64, Maybe RcvSwitchStatus, Maybe VersionSMPC, Int, ServiceAssoc)
|
||||
:. (Maybe SMP.NtfPublicAuthKey, Maybe SMP.NtfPrivateAuthKey, Maybe SMP.NotifierId, Maybe RcvNtfDhSecret)
|
||||
:. (Maybe SMP.LinkId, Maybe LinkKey, Maybe C.PrivateKeyEd25519, Maybe EncDataBytes) ->
|
||||
RcvQueue
|
||||
toRcvQueue
|
||||
( (userId, keyHash, connId, host, port, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, queueMode)
|
||||
:. (status, enableNtfs_, clientNoticeId, dbQueueId, BI primary, dbReplaceQueueId, rcvSwchStatus, smpClientVersion_, deleteErrors)
|
||||
:. (status, enableNtfs_, clientNoticeId, dbQueueId, BI primary, dbReplaceQueueId, rcvSwchStatus, smpClientVersion_, deleteErrors, rcvServiceAssoc)
|
||||
:. (ntfPublicKey_, ntfPrivateKey_, notifierId_, rcvNtfDhSecret_)
|
||||
:. (shortLinkId_, shortLinkKey_, linkPrivSigKey_, linkEncFixedData_)
|
||||
) =
|
||||
@@ -2401,7 +2488,7 @@ toRcvQueue
|
||||
_ -> Nothing
|
||||
enableNtfs = maybe True unBI enableNtfs_
|
||||
-- TODO [certs rcv] read client service
|
||||
in RcvQueue {userId, connId, server, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, queueMode, shortLink, clientService = Nothing, status, enableNtfs, clientNoticeId, dbQueueId, primary, dbReplaceQueueId, rcvSwchStatus, smpClientVersion, clientNtfCreds, deleteErrors}
|
||||
in RcvQueue {userId, connId, server, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, queueMode, shortLink, rcvServiceAssoc, status, enableNtfs, clientNoticeId, dbQueueId, primary, dbReplaceQueueId, rcvSwchStatus, smpClientVersion, clientNtfCreds, deleteErrors}
|
||||
|
||||
-- | returns all connection queue credentials, the first queue is the primary one
|
||||
getRcvQueueSubsByConnId_ :: DB.Connection -> ConnId -> IO (Maybe (NonEmpty RcvQueueSub))
|
||||
|
||||
@@ -46,6 +46,7 @@ import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250322_short_links
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250702_conn_invitations_remove_cascade_delete
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251009_queue_to_subscribe
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251010_client_notices
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251020_service_certs
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -91,7 +92,8 @@ schemaMigrations =
|
||||
("m20250322_short_links", m20250322_short_links, Just down_m20250322_short_links),
|
||||
("m20250702_conn_invitations_remove_cascade_delete", m20250702_conn_invitations_remove_cascade_delete, Just down_m20250702_conn_invitations_remove_cascade_delete),
|
||||
("m20251009_queue_to_subscribe", m20251009_queue_to_subscribe, Just down_m20251009_queue_to_subscribe),
|
||||
("m20251010_client_notices", m20251010_client_notices, Just down_m20251010_client_notices)
|
||||
("m20251010_client_notices", m20251010_client_notices, Just down_m20251010_client_notices),
|
||||
("m20251020_service_certs", m20251020_service_certs, Just down_m20251020_service_certs)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250517_service_certs where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
-- TODO move date forward, create migration for postgres
|
||||
m20250517_service_certs :: Query
|
||||
m20250517_service_certs =
|
||||
[sql|
|
||||
CREATE TABLE server_certs(
|
||||
server_cert_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
host TEXT NOT NULL,
|
||||
port TEXT NOT NULL,
|
||||
certificate BLOB NOT NULL,
|
||||
priv_key BLOB NOT NULL,
|
||||
service_id BLOB,
|
||||
FOREIGN KEY(host, port) REFERENCES servers ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_server_certs_user_id_host_port ON server_certs(user_id, host, port);
|
||||
|
||||
CREATE INDEX idx_server_certs_host_port ON server_certs(host, port);
|
||||
|
||||
ALTER TABLE rcv_queues ADD COLUMN rcv_service_id BLOB;
|
||||
|]
|
||||
|
||||
down_m20250517_service_certs :: Query
|
||||
down_m20250517_service_certs =
|
||||
[sql|
|
||||
ALTER TABLE rcv_queues DROP COLUMN rcv_service_id;
|
||||
|
||||
DROP INDEX idx_server_certs_host_port;
|
||||
|
||||
DROP INDEX idx_server_certs_user_id_host_port;
|
||||
|
||||
DROP TABLE server_certs;
|
||||
|]
|
||||
@@ -0,0 +1,40 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Messaging.Agent.Store.SQLite.Migrations.M20251020_service_certs where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
-- TODO move date forward, create migration for postgres
|
||||
m20251020_service_certs :: Query
|
||||
m20251020_service_certs =
|
||||
[sql|
|
||||
CREATE TABLE client_services(
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
host TEXT NOT NULL,
|
||||
port TEXT NOT NULL,
|
||||
service_cert BLOB NOT NULL,
|
||||
service_cert_hash BLOB NOT NULL,
|
||||
service_priv_key BLOB NOT NULL,
|
||||
rcv_service_id BLOB,
|
||||
FOREIGN KEY(host, port) REFERENCES servers ON UPDATE CASCADE ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_server_certs_user_id_host_port ON client_services(user_id, host, port);
|
||||
|
||||
CREATE INDEX idx_server_certs_host_port ON client_services(host, port);
|
||||
|
||||
ALTER TABLE rcv_queues ADD COLUMN rcv_service_assoc INTEGER NOT NULL DEFAULT 0;
|
||||
|]
|
||||
|
||||
down_m20251020_service_certs :: Query
|
||||
down_m20251020_service_certs =
|
||||
[sql|
|
||||
ALTER TABLE rcv_queues DROP COLUMN rcv_service_assoc;
|
||||
|
||||
DROP INDEX idx_server_certs_host_port;
|
||||
|
||||
DROP INDEX idx_server_certs_user_id_host_port;
|
||||
|
||||
DROP TABLE client_services;
|
||||
|]
|
||||
@@ -63,6 +63,7 @@ CREATE TABLE rcv_queues(
|
||||
to_subscribe INTEGER NOT NULL DEFAULT 0,
|
||||
client_notice_id INTEGER
|
||||
REFERENCES client_notices ON UPDATE RESTRICT ON DELETE SET NULL,
|
||||
rcv_service_assoc INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(host, port, rcv_id),
|
||||
FOREIGN KEY(host, port) REFERENCES servers
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
@@ -450,6 +451,16 @@ CREATE TABLE client_notices(
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE client_services(
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
host TEXT NOT NULL,
|
||||
port TEXT NOT NULL,
|
||||
service_cert BLOB NOT NULL,
|
||||
service_cert_hash BLOB NOT NULL,
|
||||
service_priv_key BLOB NOT NULL,
|
||||
rcv_service_id BLOB,
|
||||
FOREIGN KEY(host, port) REFERENCES servers ON UPDATE CASCADE ON DELETE RESTRICT
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_rcv_queues_ntf ON rcv_queues(host, port, ntf_id);
|
||||
CREATE UNIQUE INDEX idx_rcv_queue_id ON rcv_queues(conn_id, rcv_queue_id);
|
||||
CREATE UNIQUE INDEX idx_snd_queue_id ON snd_queues(conn_id, snd_queue_id);
|
||||
@@ -593,3 +604,9 @@ CREATE UNIQUE INDEX idx_client_notices_entity ON client_notices(
|
||||
entity_id
|
||||
);
|
||||
CREATE INDEX idx_rcv_queues_client_notice_id ON rcv_queues(client_notice_id);
|
||||
CREATE UNIQUE INDEX idx_server_certs_user_id_host_port ON client_services(
|
||||
user_id,
|
||||
host,
|
||||
port
|
||||
);
|
||||
CREATE INDEX idx_server_certs_host_port ON client_services(host, port);
|
||||
|
||||
@@ -909,12 +909,12 @@ nsubResponse_ = \case
|
||||
{-# INLINE nsubResponse_ #-}
|
||||
|
||||
-- This command is always sent in background request mode
|
||||
subscribeService :: forall p. (PartyI p, ServiceParty p) => SMPClient -> SParty p -> ExceptT SMPClientError IO Int64
|
||||
subscribeService :: forall p. (PartyI p, ServiceParty p) => SMPClient -> SParty p -> ExceptT SMPClientError IO (Int64, IdsHash)
|
||||
subscribeService c party = case smpClientService c of
|
||||
Just THClientService {serviceId, serviceKey} -> do
|
||||
liftIO $ enablePings c
|
||||
sendSMPCommand c NRMBackground (Just (C.APrivateAuthKey C.SEd25519 serviceKey)) serviceId subCmd >>= \case
|
||||
SOKS n -> pure n
|
||||
SOKS n idsHash -> pure (n, idsHash)
|
||||
r -> throwE $ unexpectedResponse r
|
||||
where
|
||||
subCmd :: Command p
|
||||
|
||||
@@ -479,14 +479,14 @@ smpSubscribeService ca smp srv serviceSub@(serviceId, _) = case smpClientService
|
||||
(True <$ processSubscription r)
|
||||
(pure False)
|
||||
if ok
|
||||
then case r of
|
||||
Right n -> notify ca $ CAServiceSubscribed srv serviceSub n
|
||||
then case r of -- TODO [certs rcv] compare hash
|
||||
Right (n, _idsHash) -> notify ca $ CAServiceSubscribed srv serviceSub n
|
||||
Left e
|
||||
| smpClientServiceError e -> notifyUnavailable
|
||||
| temporaryClientError e -> reconnectClient ca srv
|
||||
| otherwise -> notify ca $ CAServiceSubError srv serviceSub e
|
||||
else reconnectClient ca srv
|
||||
processSubscription = mapM_ $ \n -> do
|
||||
processSubscription = mapM_ $ \(n, _idsHash) -> do -- TODO [certs rcv] validate hash here?
|
||||
setActiveServiceSub ca srv $ Just ((serviceId, n), sessId)
|
||||
setPendingServiceSub ca srv Nothing
|
||||
serviceAvailable THClientService {serviceRole, serviceId = serviceId'} =
|
||||
|
||||
@@ -87,6 +87,8 @@ module Simplex.Messaging.Crypto
|
||||
signatureKeyPair,
|
||||
publicToX509,
|
||||
encodeASNObj,
|
||||
decodeASNKey,
|
||||
asnKeyError,
|
||||
|
||||
-- * key encoding/decoding
|
||||
encodePubKey,
|
||||
@@ -1493,11 +1495,11 @@ encodeASNObj k = toStrict . encodeASN1 DER $ toASN1 k []
|
||||
|
||||
-- Decoding of binary X509 'CryptoPublicKey'.
|
||||
decodePubKey :: CryptoPublicKey k => ByteString -> Either String k
|
||||
decodePubKey = decodeKey >=> x509ToPublic >=> pubKey
|
||||
decodePubKey = decodeASNKey >=> x509ToPublic >=> pubKey
|
||||
|
||||
-- Decoding of binary PKCS8 'PrivateKey'.
|
||||
decodePrivKey :: CryptoPrivateKey k => ByteString -> Either String k
|
||||
decodePrivKey = decodeKey >=> x509ToPrivate >=> privKey
|
||||
decodePrivKey = decodeASNKey >=> x509ToPrivate >=> privKey
|
||||
|
||||
x509ToPublic :: (X.PubKey, [ASN1]) -> Either String APublicKey
|
||||
x509ToPublic = \case
|
||||
@@ -1505,7 +1507,7 @@ x509ToPublic = \case
|
||||
(X.PubKeyEd448 k, []) -> Right . APublicKey SEd448 $ PublicKeyEd448 k
|
||||
(X.PubKeyX25519 k, []) -> Right . APublicKey SX25519 $ PublicKeyX25519 k
|
||||
(X.PubKeyX448 k, []) -> Right . APublicKey SX448 $ PublicKeyX448 k
|
||||
r -> keyError r
|
||||
r -> asnKeyError r
|
||||
|
||||
x509ToPublic' :: CryptoPublicKey k => X.PubKey -> Either String k
|
||||
x509ToPublic' k = x509ToPublic (k, []) >>= pubKey
|
||||
@@ -1517,16 +1519,16 @@ x509ToPrivate = \case
|
||||
(X.PrivKeyEd448 k, []) -> Right $ APrivateKey SEd448 $ PrivateKeyEd448 k
|
||||
(X.PrivKeyX25519 k, []) -> Right $ APrivateKey SX25519 $ PrivateKeyX25519 k
|
||||
(X.PrivKeyX448 k, []) -> Right $ APrivateKey SX448 $ PrivateKeyX448 k
|
||||
r -> keyError r
|
||||
r -> asnKeyError r
|
||||
|
||||
x509ToPrivate' :: CryptoPrivateKey k => X.PrivKey -> Either String k
|
||||
x509ToPrivate' pk = x509ToPrivate (pk, []) >>= privKey
|
||||
{-# INLINE x509ToPrivate' #-}
|
||||
|
||||
decodeKey :: ASN1Object a => ByteString -> Either String (a, [ASN1])
|
||||
decodeKey = fromASN1 <=< first show . decodeASN1 DER . fromStrict
|
||||
decodeASNKey :: ASN1Object a => ByteString -> Either String (a, [ASN1])
|
||||
decodeASNKey = fromASN1 <=< first show . decodeASN1 DER . fromStrict
|
||||
|
||||
keyError :: (a, [ASN1]) -> Either String b
|
||||
keyError = \case
|
||||
asnKeyError :: (a, [ASN1]) -> Either String b
|
||||
asnKeyError = \case
|
||||
(_, []) -> Left "unknown key algorithm"
|
||||
_ -> Left "more than one key"
|
||||
|
||||
@@ -140,6 +140,7 @@ module Simplex.Messaging.Protocol
|
||||
RcvMessage (..),
|
||||
MsgId,
|
||||
MsgBody,
|
||||
IdsHash,
|
||||
MaxMessageLen,
|
||||
MaxRcvMessageLen,
|
||||
EncRcvMsgBody (..),
|
||||
@@ -698,11 +699,13 @@ data BrokerMsg where
|
||||
-- | Service subscription success - confirms when queue was associated with the service
|
||||
SOK :: Maybe ServiceId -> BrokerMsg
|
||||
-- | The number of queues subscribed with SUBS command
|
||||
SOKS :: Int64 -> BrokerMsg
|
||||
SOKS :: Int64 -> IdsHash -> BrokerMsg
|
||||
-- MSG v1/2 has to be supported for encoding/decoding
|
||||
-- v1: MSG :: MsgId -> SystemTime -> MsgBody -> BrokerMsg
|
||||
-- v2: MsgId -> SystemTime -> MsgFlags -> MsgBody -> BrokerMsg
|
||||
MSG :: RcvMessage -> BrokerMsg
|
||||
-- sent once delivering messages to SUBS command is complete
|
||||
SALL :: BrokerMsg
|
||||
NID :: NotifierId -> RcvNtfPublicDhKey -> BrokerMsg
|
||||
NMSG :: C.CbNonce -> EncNMsgMeta -> BrokerMsg
|
||||
-- Should include certificate chain
|
||||
@@ -939,6 +942,7 @@ data BrokerMsgTag
|
||||
| SOK_
|
||||
| SOKS_
|
||||
| MSG_
|
||||
| SALL_
|
||||
| NID_
|
||||
| NMSG_
|
||||
| PKEY_
|
||||
@@ -1031,6 +1035,7 @@ instance Encoding BrokerMsgTag where
|
||||
SOK_ -> "SOK"
|
||||
SOKS_ -> "SOKS"
|
||||
MSG_ -> "MSG"
|
||||
SALL_ -> "SALL"
|
||||
NID_ -> "NID"
|
||||
NMSG_ -> "NMSG"
|
||||
PKEY_ -> "PKEY"
|
||||
@@ -1052,6 +1057,7 @@ instance ProtocolMsgTag BrokerMsgTag where
|
||||
"SOK" -> Just SOK_
|
||||
"SOKS" -> Just SOKS_
|
||||
"MSG" -> Just MSG_
|
||||
"SALL" -> Just SALL_
|
||||
"NID" -> Just NID_
|
||||
"NMSG" -> Just NMSG_
|
||||
"PKEY" -> Just PKEY_
|
||||
@@ -1454,6 +1460,8 @@ type MsgId = ByteString
|
||||
-- | SMP message body.
|
||||
type MsgBody = ByteString
|
||||
|
||||
type IdsHash = ByteString
|
||||
|
||||
data ProtocolErrorType = PECmdSyntax | PECmdUnknown | PESession | PEBlock
|
||||
|
||||
-- | Type for protocol errors.
|
||||
@@ -1834,9 +1842,12 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
||||
SOK serviceId_
|
||||
| v >= serviceCertsSMPVersion -> e (SOK_, ' ', serviceId_)
|
||||
| otherwise -> e OK_ -- won't happen, the association with the service requires v >= serviceCertsSMPVersion
|
||||
SOKS n -> e (SOKS_, ' ', n)
|
||||
SOKS n idsHash
|
||||
| v >= rcvServiceSMPVersion -> e (SOKS_, ' ', n, idsHash)
|
||||
| otherwise -> e (SOKS_, ' ', n)
|
||||
MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body} ->
|
||||
e (MSG_, ' ', msgId, Tail body)
|
||||
SALL -> e SALL_
|
||||
NID nId srvNtfDh -> e (NID_, ' ', nId, srvNtfDh)
|
||||
NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta)
|
||||
PKEY sid vr certKey -> e (PKEY_, ' ', sid, vr, certKey)
|
||||
@@ -1867,6 +1878,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
||||
MSG . RcvMessage msgId <$> bodyP
|
||||
where
|
||||
bodyP = EncRcvMsgBody . unTail <$> smpP
|
||||
SALL_ -> pure SALL
|
||||
IDS_
|
||||
| v >= newNtfCredsSMPVersion -> ids smpP smpP smpP smpP
|
||||
| v >= serviceCertsSMPVersion -> ids smpP smpP smpP nothing
|
||||
@@ -1887,7 +1899,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
||||
pure $ IDS QIK {rcvId, sndId, rcvPublicDhKey, queueMode, linkId, serviceId, serverNtfCreds}
|
||||
LNK_ -> LNK <$> _smpP <*> smpP
|
||||
SOK_ -> SOK <$> _smpP
|
||||
SOKS_ -> SOKS <$> _smpP
|
||||
SOKS_
|
||||
| v >= rcvServiceSMPVersion -> SOKS <$> _smpP <*> smpP
|
||||
| otherwise -> SOKS <$> _smpP <*> pure B.empty
|
||||
NID_ -> NID <$> _smpP <*> smpP
|
||||
NMSG_ -> NMSG <$> _smpP <*> smpP
|
||||
PKEY_ -> PKEY <$> _smpP <*> smpP <*> smpP
|
||||
@@ -1917,6 +1931,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
||||
PONG -> noEntityMsg
|
||||
PKEY {} -> noEntityMsg
|
||||
RRES _ -> noEntityMsg
|
||||
SALL -> noEntityMsg
|
||||
-- other broker responses must have queue ID
|
||||
_
|
||||
| B.null entId -> Left $ CMD NO_ENTITY
|
||||
|
||||
+146
-72
@@ -1359,7 +1359,7 @@ client
|
||||
-- TODO [certs rcv] rcv subscriptions
|
||||
Server {subscribers, ntfSubscribers}
|
||||
ms
|
||||
clnt@Client {clientId, ntfSubscriptions, ntfServiceSubscribed, serviceSubsCount = _todo', ntfServiceSubsCount, rcvQ, sndQ, clientTHParams = thParams'@THandleParams {sessionId}, procThreads} = do
|
||||
clnt@Client {clientId, rcvQ, sndQ, msgQ, clientTHParams = thParams'@THandleParams {sessionId}, procThreads} = do
|
||||
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
|
||||
let THandleParams {thVersion} = thParams'
|
||||
clntServiceId = (\THClientService {serviceId} -> serviceId) <$> (peerClientService =<< thAuth thParams')
|
||||
@@ -1495,7 +1495,9 @@ client
|
||||
OFF -> response <$> maybe (pure $ err INTERNAL) suspendQueue_ q_
|
||||
DEL -> response <$> maybe (pure $ err INTERNAL) delQueueAndMsgs q_
|
||||
QUE -> withQueue $ \q qr -> (corrId,entId,) <$> getQueueInfo q qr
|
||||
Cmd SRecipientService SUBS -> pure $ response $ err (CMD PROHIBITED) -- "TODO [certs rcv]"
|
||||
Cmd SRecipientService SUBS -> response . (corrId,entId,) <$> case clntServiceId of
|
||||
Just serviceId -> subscribeServiceMessages serviceId
|
||||
Nothing -> pure $ ERR INTERNAL -- it's "internal" because it should never get to this branch
|
||||
where
|
||||
createQueue :: NewQueueReq -> M s (Transmission BrokerMsg)
|
||||
createQueue NewQueueReq {rcvAuthKey, rcvDhKey, subMode, queueReqData, ntfCreds}
|
||||
@@ -1615,11 +1617,13 @@ client
|
||||
suspendQueue_ :: (StoreQueue s, QueueRec) -> M s (Transmission BrokerMsg)
|
||||
suspendQueue_ (q, _) = liftIO $ either err (const ok) <$> suspendQueue (queueStore ms) q
|
||||
|
||||
-- TODO [certs rcv] if serviceId is passed, associate with the service and respond with SOK
|
||||
subscribeQueueAndDeliver :: StoreQueue s -> QueueRec -> M s ResponseAndMessage
|
||||
subscribeQueueAndDeliver q qr =
|
||||
subscribeQueueAndDeliver q qr@QueueRec {rcvServiceId} =
|
||||
liftIO (TM.lookupIO entId $ subscriptions clnt) >>= \case
|
||||
Nothing -> subscribeRcvQueue qr >>= deliver False
|
||||
Nothing ->
|
||||
sharedSubscribeQueue q SRecipientService rcvServiceId subscribers subscriptions serviceSubsCount (newSubscription NoSub) rcvServices >>= \case
|
||||
Left e -> pure (err e, Nothing)
|
||||
Right s -> deliver s
|
||||
Just s@Sub {subThread} -> do
|
||||
stats <- asks serverStats
|
||||
case subThread of
|
||||
@@ -1629,27 +1633,29 @@ client
|
||||
pure (err (CMD PROHIBITED), Nothing)
|
||||
_ -> do
|
||||
incStat $ qSubDuplicate stats
|
||||
atomically (writeTVar (delivered s) Nothing) >> deliver True s
|
||||
atomically (writeTVar (delivered s) Nothing) >> deliver (True, Just s)
|
||||
where
|
||||
deliver :: Bool -> Sub -> M s ResponseAndMessage
|
||||
deliver hasSub sub = do
|
||||
deliver :: (Bool, Maybe Sub) -> M s ResponseAndMessage
|
||||
deliver (hasSub, sub_) = do
|
||||
stats <- asks serverStats
|
||||
fmap (either ((,Nothing) . err) id) $ liftIO $ runExceptT $ do
|
||||
msg_ <- tryPeekMsg ms q
|
||||
msg' <- forM msg_ $ \msg -> liftIO $ do
|
||||
ts <- getSystemSeconds
|
||||
sub <- maybe (atomically getSub) pure sub_
|
||||
atomically $ setDelivered sub msg ts
|
||||
unless hasSub $ incStat $ qSub stats
|
||||
pure (NoCorrId, entId, MSG (encryptMsg qr msg))
|
||||
pure ((corrId, entId, SOK clntServiceId), msg')
|
||||
|
||||
-- TODO [certs rcv] combine with subscribing ntf queues
|
||||
subscribeRcvQueue :: QueueRec -> M s Sub
|
||||
subscribeRcvQueue QueueRec {rcvServiceId} = atomically $ do
|
||||
writeTQueue (subQ subscribers) (CSClient entId rcvServiceId Nothing, clientId)
|
||||
sub <- newSubscription NoSub
|
||||
TM.insert entId sub $ subscriptions clnt
|
||||
pure sub
|
||||
getSub :: STM Sub
|
||||
getSub =
|
||||
TM.lookup entId (subscriptions clnt) >>= \case
|
||||
Just sub -> pure sub
|
||||
Nothing -> do
|
||||
sub <- newSubscription NoSub
|
||||
TM.insert entId sub $ subscriptions clnt
|
||||
pure sub
|
||||
|
||||
subscribeNewQueue :: RecipientId -> QueueRec -> M s ()
|
||||
subscribeNewQueue rId QueueRec {rcvServiceId} = do
|
||||
@@ -1719,74 +1725,131 @@ client
|
||||
else liftIO (updateQueueTime (queueStore ms) q t) >>= either (pure . err') (action q)
|
||||
|
||||
subscribeNotifications :: StoreQueue s -> NtfCreds -> M s BrokerMsg
|
||||
subscribeNotifications q NtfCreds {ntfServiceId} = do
|
||||
subscribeNotifications q NtfCreds {ntfServiceId} =
|
||||
sharedSubscribeQueue q SNotifierService ntfServiceId ntfSubscribers ntfSubscriptions ntfServiceSubsCount (pure ()) ntfServices >>= \case
|
||||
Left e -> pure $ ERR e
|
||||
Right (hasSub, _) -> do
|
||||
when (isNothing clntServiceId) $
|
||||
asks serverStats >>= incStat . (if hasSub then ntfSubDuplicate else ntfSub)
|
||||
pure $ SOK clntServiceId
|
||||
|
||||
sharedSubscribeQueue ::
|
||||
(PartyI p, ServiceParty p) =>
|
||||
StoreQueue s ->
|
||||
SParty p ->
|
||||
Maybe ServiceId ->
|
||||
ServerSubscribers s ->
|
||||
(Client s -> TMap QueueId sub) ->
|
||||
(Client s -> TVar Int64) ->
|
||||
STM sub ->
|
||||
(ServerStats -> ServiceStats) ->
|
||||
M s (Either ErrorType (Bool, Maybe sub))
|
||||
sharedSubscribeQueue q party queueServiceId srvSubscribers clientSubs clientServiceSubs mkSub servicesSel = do
|
||||
stats <- asks serverStats
|
||||
let incNtfSrvStat sel = incStat $ sel $ ntfServices stats
|
||||
case clntServiceId of
|
||||
let incSrvStat sel = incStat $ sel $ servicesSel stats
|
||||
writeSub = writeTQueue (subQ srvSubscribers) (CSClient entId queueServiceId clntServiceId, clientId)
|
||||
liftIO $ case clntServiceId of
|
||||
Just serviceId
|
||||
| ntfServiceId == Just serviceId -> do
|
||||
| queueServiceId == Just serviceId -> do
|
||||
-- duplicate queue-service association - can only happen in case of response error/timeout
|
||||
hasSub <- atomically $ ifM hasServiceSub (pure True) (False <$ newServiceQueueSub)
|
||||
hasSub <- atomically $ ifM hasServiceSub (pure True) (False <$ incServiceQueueSubs)
|
||||
unless hasSub $ do
|
||||
incNtfSrvStat srvSubCount
|
||||
incNtfSrvStat srvSubQueues
|
||||
incNtfSrvStat srvAssocDuplicate
|
||||
pure $ SOK $ Just serviceId
|
||||
| otherwise ->
|
||||
atomically writeSub
|
||||
incSrvStat srvSubCount
|
||||
incSrvStat srvSubQueues
|
||||
incSrvStat srvAssocDuplicate
|
||||
pure $ Right (hasSub, Nothing)
|
||||
| otherwise -> runExceptT $ do
|
||||
-- new or updated queue-service association
|
||||
liftIO (setQueueService (queueStore ms) q SNotifierService (Just serviceId)) >>= \case
|
||||
Left e -> pure $ ERR e
|
||||
Right () -> do
|
||||
hasSub <- atomically $ (<$ newServiceQueueSub) =<< hasServiceSub
|
||||
unless hasSub $ incNtfSrvStat srvSubCount
|
||||
incNtfSrvStat srvSubQueues
|
||||
incNtfSrvStat $ maybe srvAssocNew (const srvAssocUpdated) ntfServiceId
|
||||
pure $ SOK $ Just serviceId
|
||||
ExceptT $ setQueueService (queueStore ms) q party (Just serviceId)
|
||||
hasSub <- atomically $ (<$ incServiceQueueSubs) =<< hasServiceSub
|
||||
atomically writeSub
|
||||
liftIO $ do
|
||||
unless hasSub $ incSrvStat srvSubCount
|
||||
incSrvStat srvSubQueues
|
||||
incSrvStat $ maybe srvAssocNew (const srvAssocUpdated) queueServiceId
|
||||
pure (hasSub, Nothing)
|
||||
where
|
||||
hasServiceSub = (0 /=) <$> readTVar ntfServiceSubsCount
|
||||
-- This function is used when queue is associated with the service.
|
||||
newServiceQueueSub = do
|
||||
writeTQueue (subQ ntfSubscribers) (CSClient entId ntfServiceId (Just serviceId), clientId)
|
||||
modifyTVar' ntfServiceSubsCount (+ 1) -- service count
|
||||
modifyTVar' (totalServiceSubs ntfSubscribers) (+ 1) -- server count for all services
|
||||
Nothing -> case ntfServiceId of
|
||||
Just _ ->
|
||||
liftIO (setQueueService (queueStore ms) q SNotifierService Nothing) >>= \case
|
||||
Left e -> pure $ ERR e
|
||||
Right () -> do
|
||||
-- hasSubscription should never be True in this branch, because queue was associated with service.
|
||||
-- So unless storage and session states diverge, this check is redundant.
|
||||
hasSub <- atomically $ hasSubscription >>= newSub
|
||||
incNtfSrvStat srvAssocRemoved
|
||||
sok hasSub
|
||||
hasServiceSub = (0 /=) <$> readTVar (clientServiceSubs clnt)
|
||||
-- This function is used when queue association with the service is created.
|
||||
incServiceQueueSubs = modifyTVar' (clientServiceSubs clnt) (+ 1) -- service count
|
||||
Nothing -> case queueServiceId of
|
||||
Just _ -> runExceptT $ do
|
||||
ExceptT $ setQueueService (queueStore ms) q party Nothing
|
||||
liftIO $ incSrvStat srvAssocRemoved
|
||||
-- getSubscription may be Just for receiving service, where clientSubs also hold active deliveries for service subscriptions.
|
||||
-- For notification service it can only be Just if storage and session states diverge.
|
||||
r <- atomically $ getSubscription >>= newSub
|
||||
atomically writeSub
|
||||
pure r
|
||||
Nothing -> do
|
||||
hasSub <- atomically $ ifM hasSubscription (pure True) (newSub False)
|
||||
sok hasSub
|
||||
r@(hasSub, _) <- atomically $ getSubscription >>= newSub
|
||||
unless hasSub $ atomically writeSub
|
||||
pure $ Right r
|
||||
where
|
||||
hasSubscription = TM.member entId ntfSubscriptions
|
||||
newSub hasSub = do
|
||||
writeTQueue (subQ ntfSubscribers) (CSClient entId ntfServiceId Nothing, clientId)
|
||||
unless (hasSub) $ TM.insert entId () ntfSubscriptions
|
||||
pure hasSub
|
||||
sok hasSub = do
|
||||
incStat $ if hasSub then ntfSubDuplicate stats else ntfSub stats
|
||||
pure $ SOK Nothing
|
||||
getSubscription = TM.lookup entId $ clientSubs clnt
|
||||
newSub = \case
|
||||
Just sub -> pure (True, Just sub)
|
||||
Nothing -> do
|
||||
sub <- mkSub
|
||||
TM.insert entId sub $ clientSubs clnt
|
||||
pure (False, Just sub)
|
||||
|
||||
subscribeServiceMessages :: ServiceId -> M s BrokerMsg
|
||||
subscribeServiceMessages serviceId =
|
||||
sharedSubscribeService SRecipientService serviceId subscribers serviceSubscribed serviceSubsCount >>= \case
|
||||
Left e -> pure $ ERR e
|
||||
Right (hasSub, (count, idsHash)) -> do
|
||||
unless hasSub $ forkClient clnt "deliverServiceMessages" $ liftIO $ deliverServiceMessages count
|
||||
pure $ SOKS count idsHash
|
||||
where
|
||||
deliverServiceMessages expectedCnt = do
|
||||
(qCnt, _msgCnt, _dupCnt, _errCnt) <- foldRcvServiceMessages ms serviceId deliverQueueMsg (0, 0, 0, 0)
|
||||
atomically $ writeTBQueue msgQ [(NoCorrId, NoEntity, SALL)]
|
||||
-- TODO [cert rcv] compare with expected
|
||||
logNote $ "Service subscriptions for " <> tshow serviceId <> " (" <> tshow qCnt <> " queues)"
|
||||
deliverQueueMsg :: (Int, Int, Int, Int) -> RecipientId -> Either ErrorType (Maybe (QueueRec, Message)) -> IO (Int, Int, Int, Int)
|
||||
deliverQueueMsg (!qCnt, !msgCnt, !dupCnt, !errCnt) rId = \case
|
||||
Left e -> pure (qCnt + 1, msgCnt, dupCnt, errCnt + 1) -- TODO [certs rcv] deliver subscription error
|
||||
Right qMsg_ -> case qMsg_ of
|
||||
Nothing -> pure (qCnt + 1, msgCnt, dupCnt, errCnt)
|
||||
Just (qr, msg) ->
|
||||
atomically (getSubscription rId) >>= \case
|
||||
Nothing -> pure (qCnt + 1, msgCnt, dupCnt + 1, errCnt)
|
||||
Just sub -> do
|
||||
ts <- getSystemSeconds
|
||||
atomically $ setDelivered sub msg ts
|
||||
atomically $ writeTBQueue msgQ [(NoCorrId, rId, MSG (encryptMsg qr msg))]
|
||||
pure (qCnt + 1, msgCnt + 1, dupCnt, errCnt)
|
||||
getSubscription rId =
|
||||
TM.lookup rId (subscriptions clnt) >>= \case
|
||||
-- If delivery subscription already exists, then there is no need to deliver message.
|
||||
-- It may have been created when the message is sent after service subscription is created.
|
||||
Just _sub -> pure Nothing
|
||||
Nothing -> do
|
||||
sub <- newSubscription NoSub
|
||||
TM.insert rId sub $ subscriptions clnt
|
||||
pure $ Just sub
|
||||
|
||||
subscribeServiceNotifications :: ServiceId -> M s BrokerMsg
|
||||
subscribeServiceNotifications serviceId = do
|
||||
subscribed <- readTVarIO ntfServiceSubscribed
|
||||
if subscribed
|
||||
then SOKS <$> readTVarIO ntfServiceSubsCount
|
||||
else
|
||||
liftIO (getServiceQueueCount @(StoreQueue s) (queueStore ms) SNotifierService serviceId) >>= \case
|
||||
Left e -> pure $ ERR e
|
||||
Right !count' -> do
|
||||
subscribeServiceNotifications serviceId =
|
||||
either ERR (uncurry SOKS . snd) <$> sharedSubscribeService SNotifierService serviceId ntfSubscribers ntfServiceSubscribed ntfServiceSubsCount
|
||||
|
||||
sharedSubscribeService :: (PartyI p, ServiceParty p) => SParty p -> ServiceId -> ServerSubscribers s -> (Client s -> TVar Bool) -> (Client s -> TVar Int64) -> M s (Either ErrorType (Bool, (Int64, IdsHash)))
|
||||
sharedSubscribeService party serviceId srvSubscribers clientServiceSubscribed clientServiceSubs = do
|
||||
subscribed <- readTVarIO $ clientServiceSubscribed clnt
|
||||
liftIO $ runExceptT $
|
||||
(subscribed,)
|
||||
<$> if subscribed
|
||||
then (,B.empty) <$> readTVarIO (clientServiceSubs clnt) -- TODO [certs rcv] get IDs hash
|
||||
else do
|
||||
count' <- ExceptT $ getServiceQueueCount @(StoreQueue s) (queueStore ms) party serviceId
|
||||
incCount <- atomically $ do
|
||||
writeTVar ntfServiceSubscribed True
|
||||
count <- swapTVar ntfServiceSubsCount count'
|
||||
writeTVar (clientServiceSubscribed clnt) True
|
||||
count <- swapTVar (clientServiceSubs clnt) count'
|
||||
pure $ count' - count
|
||||
atomically $ writeTQueue (subQ ntfSubscribers) (CSService serviceId incCount, clientId)
|
||||
pure $ SOKS count'
|
||||
atomically $ writeTQueue (subQ srvSubscribers) (CSService serviceId incCount, clientId)
|
||||
pure (count', B.empty) -- TODO [certs rcv] get IDs hash
|
||||
|
||||
acknowledgeMsg :: MsgId -> StoreQueue s -> QueueRec -> M s (Transmission BrokerMsg)
|
||||
acknowledgeMsg msgId q qr =
|
||||
@@ -1904,10 +1967,13 @@ client
|
||||
tryDeliverMessage msg =
|
||||
-- the subscribed client var is read outside of STM to avoid transaction cost
|
||||
-- in case no client is subscribed.
|
||||
getSubscribedClient rId (queueSubscribers subscribers)
|
||||
getSubscribed
|
||||
$>>= deliverToSub
|
||||
>>= mapM_ forkDeliver
|
||||
where
|
||||
getSubscribed = case rcvServiceId qr of
|
||||
Just serviceId -> getSubscribedClient serviceId $ serviceSubscribers subscribers
|
||||
Nothing -> getSubscribedClient rId $ queueSubscribers subscribers
|
||||
rId = recipientId q
|
||||
deliverToSub rcv = do
|
||||
ts <- getSystemSeconds
|
||||
@@ -1918,6 +1984,7 @@ client
|
||||
-- the new client will receive message in response to SUB.
|
||||
readTVar rcv
|
||||
$>>= \rc@Client {subscriptions = subs, sndQ = sndQ'} -> TM.lookup rId subs
|
||||
>>= maybe (newServiceDeliverySub subs) (pure . Just)
|
||||
$>>= \s@Sub {subThread, delivered} -> case subThread of
|
||||
ProhibitSub -> pure Nothing
|
||||
ServerSub st -> readTVar st >>= \case
|
||||
@@ -1930,6 +1997,12 @@ client
|
||||
(writeTVar st SubPending $> Just (rc, s, st))
|
||||
(deliver sndQ' s ts $> Nothing)
|
||||
_ -> pure Nothing
|
||||
newServiceDeliverySub subs
|
||||
| isJust (rcvServiceId qr) = do
|
||||
sub <- newSubscription NoSub
|
||||
TM.insert rId sub subs
|
||||
pure $ Just sub
|
||||
| otherwise = pure Nothing
|
||||
deliver sndQ' s ts = do
|
||||
let encMsg = encryptMsg qr msg
|
||||
writeTBQueue sndQ' ([(NoCorrId, rId, MSG encMsg)], [])
|
||||
@@ -2051,6 +2124,7 @@ client
|
||||
-- we delete subscription here, so the client with no subscriptions can be disconnected.
|
||||
sub <- atomically $ TM.lookupDelete entId $ subscriptions clnt
|
||||
liftIO $ mapM_ cancelSub sub
|
||||
when (isJust rcvServiceId) $ atomically $ modifyTVar' (serviceSubsCount clnt) $ \n -> max 0 (n - 1)
|
||||
atomically $ writeTQueue (subQ subscribers) (CSDeleted entId rcvServiceId, clientId)
|
||||
forM_ (notifier qr) $ \NtfCreds {notifierId = nId, ntfServiceId} -> do
|
||||
-- queue is deleted by a different client from the one subscribed to notifications,
|
||||
|
||||
@@ -444,6 +444,26 @@ instance MsgStoreClass (JournalMsgStore s) where
|
||||
getLoadedQueue :: JournalQueue s -> IO (JournalQueue s)
|
||||
getLoadedQueue q = fromMaybe q <$> TM.lookupIO (recipientId q) (loadedQueues $ queueStore_ ms)
|
||||
|
||||
foldRcvServiceMessages :: JournalMsgStore s -> ServiceId -> (a -> RecipientId -> Either ErrorType (Maybe (QueueRec, Message)) -> IO a) -> a -> IO a
|
||||
foldRcvServiceMessages ms serviceId f acc = case queueStore_ ms of
|
||||
MQStore st -> foldRcvServiceQueues st serviceId f' acc
|
||||
where
|
||||
f' a (q, qr) = runExceptT (tryPeekMsg ms q) >>= f a (recipientId q) . ((qr,) <$$>)
|
||||
#if defined(dbServerPostgres)
|
||||
PQStore st -> foldRcvServiceQueueRecs st serviceId f' acc
|
||||
where
|
||||
JournalMsgStore {queueLocks, sharedLock} = ms
|
||||
f' a (rId, qr) = do
|
||||
q <- mkQueue ms False rId qr
|
||||
qMsg_ <-
|
||||
withSharedWaitLock rId queueLocks sharedLock $ runExceptT $ tryStore' "foldRcvServiceMessages" rId $
|
||||
(qr,) . snd <$$> (getLoadedQueue q >>= unStoreIO . getPeekMsgQueue ms)
|
||||
f a rId qMsg_
|
||||
-- Use cached queue if available.
|
||||
-- Also see the comment in loadQueue in PostgresQueueStore
|
||||
getLoadedQueue q = fromMaybe q <$> TM.lookupIO (recipientId q) (loadedQueues $ queueStore_ ms)
|
||||
#endif
|
||||
|
||||
logQueueStates :: JournalMsgStore s -> IO ()
|
||||
logQueueStates ms = withActiveMsgQueues ms $ unStoreIO . logQueueState
|
||||
|
||||
|
||||
@@ -119,6 +119,34 @@ instance MsgStoreClass PostgresMsgStore where
|
||||
toMessageStats (expiredMsgsCount, storedMsgsCount, storedQueues) =
|
||||
MessageStats {expiredMsgsCount, storedMsgsCount, storedQueues}
|
||||
|
||||
foldRcvServiceMessages :: PostgresMsgStore -> ServiceId -> (a -> RecipientId -> Either ErrorType (Maybe (QueueRec, Message)) -> IO a) -> a -> IO a
|
||||
foldRcvServiceMessages ms serviceId f acc =
|
||||
withTransaction (dbStore $ queueStore_ ms) $ \db ->
|
||||
DB.fold
|
||||
db
|
||||
[sql|
|
||||
SELECT q.recipient_id, q.recipient_keys, q.rcv_dh_secret,
|
||||
q.sender_id, q.sender_key, q.queue_mode,
|
||||
q.notifier_id, q.notifier_key, q.rcv_ntf_dh_secret, q.ntf_service_id,
|
||||
q.status, q.updated_at, q.link_id, q.rcv_service_id,
|
||||
m.msg_id, m.msg_ts, m.msg_quota, m.msg_ntf_flag, m.msg_body
|
||||
FROM msg_queues q
|
||||
LEFT JOIN (
|
||||
SELECT recipient_id, msg_id, msg_ts, msg_quota, msg_ntf_flag, msg_body,
|
||||
ROW_NUMBER() OVER (PARTITION BY recipient_id ORDER BY message_id ASC) AS row_num
|
||||
FROM messages
|
||||
) m ON q.recipient_id = m.recipient_id AND m.row_num = 1
|
||||
WHERE q.rcv_service_id = ? AND q.deleted_at IS NULL;
|
||||
|]
|
||||
(Only serviceId)
|
||||
acc
|
||||
f'
|
||||
where
|
||||
f' a (qRow :. mRow) =
|
||||
let (rId, qr) = rowToQueueRec qRow
|
||||
msg_ = toMaybeMessage mRow
|
||||
in f a rId $ Right ((qr,) <$> msg_)
|
||||
|
||||
logQueueStates _ = error "logQueueStates not used"
|
||||
|
||||
logQueueState _ = error "logQueueState not used"
|
||||
@@ -247,6 +275,11 @@ uninterruptibleMask_ :: ExceptT ErrorType IO a -> ExceptT ErrorType IO a
|
||||
uninterruptibleMask_ = ExceptT . E.uninterruptibleMask_ . runExceptT
|
||||
{-# INLINE uninterruptibleMask_ #-}
|
||||
|
||||
toMaybeMessage :: (Maybe (Binary MsgId), Maybe Int64, Maybe Bool, Maybe Bool, Maybe (Binary MsgBody)) -> Maybe Message
|
||||
toMaybeMessage = \case
|
||||
(Just msgId, Just ts, Just msgQuota, Just ntf, Just body) -> Just $ toMessage (msgId, ts, msgQuota, ntf, body)
|
||||
_ -> Nothing
|
||||
|
||||
toMessage :: (Binary MsgId, Int64, Bool, Bool, Binary MsgBody) -> Message
|
||||
toMessage (Binary msgId, ts, msgQuota, ntf, Binary body)
|
||||
| msgQuota = MessageQuota {msgId, msgTs}
|
||||
|
||||
@@ -87,6 +87,11 @@ instance MsgStoreClass STMMsgStore where
|
||||
expireOldMessages _tty ms now ttl =
|
||||
withLoadedQueues (queueStore_ ms) $ atomically . expireQueueMsgs ms now (now - ttl)
|
||||
|
||||
foldRcvServiceMessages :: STMMsgStore -> ServiceId -> (a -> RecipientId -> Either ErrorType (Maybe (QueueRec, Message)) -> IO a) -> a -> IO a
|
||||
foldRcvServiceMessages ms serviceId f=
|
||||
foldRcvServiceQueues (queueStore_ ms) serviceId $ \a (q, qr) ->
|
||||
runExceptT (tryPeekMsg ms q) >>= f a (recipientId q) . ((qr,) <$$>)
|
||||
|
||||
logQueueStates _ = pure ()
|
||||
{-# INLINE logQueueStates #-}
|
||||
logQueueState _ = pure ()
|
||||
|
||||
@@ -45,6 +45,7 @@ class (Monad (StoreMonad s), QueueStoreClass (StoreQueue s) (QueueStore s)) => M
|
||||
unsafeWithAllMsgQueues :: Monoid a => Bool -> s -> (StoreQueue s -> IO a) -> IO a
|
||||
-- tty, store, now, ttl
|
||||
expireOldMessages :: Bool -> s -> Int64 -> Int64 -> IO MessageStats
|
||||
foldRcvServiceMessages :: s -> ServiceId -> (a -> RecipientId -> Either ErrorType (Maybe (QueueRec, Message)) -> IO a) -> a -> IO a
|
||||
logQueueStates :: s -> IO ()
|
||||
logQueueState :: StoreQueue s -> StoreMonad s ()
|
||||
queueStore :: s -> QueueStore s
|
||||
|
||||
@@ -24,9 +24,11 @@ module Simplex.Messaging.Server.QueueStore.Postgres
|
||||
batchInsertServices,
|
||||
batchInsertQueues,
|
||||
foldServiceRecs,
|
||||
foldRcvServiceQueueRecs,
|
||||
foldQueueRecs,
|
||||
foldRecentQueueRecs,
|
||||
handleDuplicate,
|
||||
rowToQueueRec,
|
||||
withLog_,
|
||||
withDB,
|
||||
withDB',
|
||||
@@ -577,12 +579,17 @@ insertServiceQuery =
|
||||
VALUES (?,?,?,?,?)
|
||||
|]
|
||||
|
||||
foldServiceRecs :: forall a q. Monoid a => PostgresQueueStore q -> (ServiceRec -> IO a) -> IO a
|
||||
foldServiceRecs :: Monoid a => PostgresQueueStore q -> (ServiceRec -> IO a) -> IO a
|
||||
foldServiceRecs st f =
|
||||
withTransaction (dbStore st) $ \db ->
|
||||
DB.fold_ db "SELECT service_id, service_role, service_cert, service_cert_hash, created_at FROM services" mempty $
|
||||
\ !acc -> fmap (acc <>) . f . rowToServiceRec
|
||||
|
||||
foldRcvServiceQueueRecs :: PostgresQueueStore q -> ServiceId -> (a -> (RecipientId, QueueRec) -> IO a) -> a -> IO a
|
||||
foldRcvServiceQueueRecs st serviceId f acc =
|
||||
withTransaction (dbStore st) $ \db ->
|
||||
DB.fold db (queueRecQuery <> " WHERE rcv_service_id = ? AND deleted_at IS NULL") (Only serviceId) acc $ \a -> f a . rowToQueueRec
|
||||
|
||||
foldQueueRecs :: Monoid a => Bool -> Bool -> PostgresQueueStore q -> ((RecipientId, QueueRec) -> IO a) -> IO a
|
||||
foldQueueRecs withData = foldQueueRecs_ foldRecs
|
||||
where
|
||||
@@ -769,10 +776,6 @@ instance ToField SMPServiceRole where toField = toField . decodeLatin1 . smpEnco
|
||||
|
||||
instance FromField SMPServiceRole where fromField = fromTextField_ $ eitherToMaybe . smpDecode . encodeUtf8
|
||||
|
||||
instance ToField X.CertificateChain where toField = toField . Binary . smpEncode . C.encodeCertChain
|
||||
|
||||
instance FromField X.CertificateChain where fromField = blobFieldDecoder (parseAll C.certChainP)
|
||||
|
||||
#if !defined(dbPostgres)
|
||||
instance ToField EntityId where toField (EntityId s) = toField $ Binary s
|
||||
|
||||
@@ -797,4 +800,8 @@ deriving newtype instance FromField EncDataBytes
|
||||
deriving newtype instance ToField (RoundedSystemTime t)
|
||||
|
||||
deriving newtype instance FromField (RoundedSystemTime t)
|
||||
|
||||
instance ToField X.CertificateChain where toField = toField . Binary . smpEncode . C.encodeCertChain
|
||||
|
||||
instance FromField X.CertificateChain where fromField = blobFieldDecoder (parseAll C.certChainP)
|
||||
#endif
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
module Simplex.Messaging.Server.QueueStore.STM
|
||||
( STMQueueStore (..),
|
||||
STMService (..),
|
||||
foldRcvServiceQueues,
|
||||
setStoreLog,
|
||||
withLog',
|
||||
readQueueRecIO,
|
||||
@@ -45,7 +46,7 @@ import Simplex.Messaging.SystemTime
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Transport (SMPServiceRole (..))
|
||||
import Simplex.Messaging.Util (anyM, ifM, tshow, ($>>), ($>>=), (<$$))
|
||||
import Simplex.Messaging.Util (anyM, ifM, tshow, ($>>), ($>>=), (<$$), (<$$>))
|
||||
import System.IO
|
||||
import UnliftIO.STM
|
||||
|
||||
@@ -359,6 +360,16 @@ instance StoreQueueClass q => QueueStoreClass q (STMQueueStore q) where
|
||||
SRecipientService -> serviceRcvQueues
|
||||
SNotifierService -> serviceNtfQueues
|
||||
|
||||
foldRcvServiceQueues :: StoreQueueClass q => STMQueueStore q -> ServiceId -> (a -> (q, QueueRec) -> IO a) -> a -> IO a
|
||||
foldRcvServiceQueues st serviceId f acc =
|
||||
TM.lookupIO serviceId (services st) >>= \case
|
||||
Nothing -> pure acc
|
||||
Just s ->
|
||||
readTVarIO (serviceRcvQueues s)
|
||||
>>= foldM (\a -> get >=> maybe (pure a) (f a)) acc
|
||||
where
|
||||
get rId = TM.lookupIO rId (queues st) $>>= \q -> (q,) <$$> readTVarIO (queueRec q)
|
||||
|
||||
withQueueRec :: TVar (Maybe QueueRec) -> (QueueRec -> STM a) -> IO (Either ErrorType a)
|
||||
withQueueRec qr a = atomically $ readQueueRec qr >>= mapM a
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ module Simplex.Messaging.Transport
|
||||
serviceCertsSMPVersion,
|
||||
newNtfCredsSMPVersion,
|
||||
clientNoticesSMPVersion,
|
||||
rcvServiceSMPVersion,
|
||||
simplexMQVersion,
|
||||
smpBlockSize,
|
||||
TransportConfig (..),
|
||||
@@ -170,6 +171,7 @@ smpBlockSize = 16384
|
||||
-- 16 - service certificates (5/31/2025)
|
||||
-- 17 - create notification credentials with NEW (7/12/2025)
|
||||
-- 18 - support client notices (10/10/2025)
|
||||
-- 19 - service subscriptions to messages (10/20/2025)
|
||||
|
||||
data SMPVersion
|
||||
|
||||
@@ -218,6 +220,9 @@ newNtfCredsSMPVersion = VersionSMP 17
|
||||
clientNoticesSMPVersion :: VersionSMP
|
||||
clientNoticesSMPVersion = VersionSMP 18
|
||||
|
||||
rcvServiceSMPVersion :: VersionSMP
|
||||
rcvServiceSMPVersion = VersionSMP 19
|
||||
|
||||
minClientSMPRelayVersion :: VersionSMP
|
||||
minClientSMPRelayVersion = VersionSMP 6
|
||||
|
||||
@@ -225,13 +230,13 @@ minServerSMPRelayVersion :: VersionSMP
|
||||
minServerSMPRelayVersion = VersionSMP 6
|
||||
|
||||
currentClientSMPRelayVersion :: VersionSMP
|
||||
currentClientSMPRelayVersion = VersionSMP 18
|
||||
currentClientSMPRelayVersion = VersionSMP 19
|
||||
|
||||
legacyServerSMPRelayVersion :: VersionSMP
|
||||
legacyServerSMPRelayVersion = VersionSMP 6
|
||||
|
||||
currentServerSMPRelayVersion :: VersionSMP
|
||||
currentServerSMPRelayVersion = VersionSMP 18
|
||||
currentServerSMPRelayVersion = VersionSMP 19
|
||||
|
||||
-- Max SMP protocol version to be used in e2e encrypted
|
||||
-- connection between client and server, as defined by SMP proxy.
|
||||
@@ -239,7 +244,7 @@ currentServerSMPRelayVersion = VersionSMP 18
|
||||
-- to prevent client version fingerprinting by the
|
||||
-- destination relays when clients upgrade at different times.
|
||||
proxiedSMPRelayVersion :: VersionSMP
|
||||
proxiedSMPRelayVersion = VersionSMP 17
|
||||
proxiedSMPRelayVersion = VersionSMP 18
|
||||
|
||||
-- minimal supported protocol version is 6
|
||||
-- TODO remove code that supports sending commands without batching
|
||||
@@ -823,7 +828,7 @@ smpClientHandshake c ks_ keyHash@(C.KeyHash kh) vRange proxyServer serviceKeys_
|
||||
serviceKeys = case serviceKeys_ of
|
||||
Just sks | v >= serviceCertsSMPVersion && certificateSent c -> Just sks
|
||||
_ -> Nothing
|
||||
clientService = mkClientService <$> serviceKeys
|
||||
clientService = mkClientService v =<< serviceKeys
|
||||
hs = SMPClientHandshake {smpVersion = v, keyHash, authPubKey = fst <$> ks_, proxyServer, clientService}
|
||||
sendHandshake th hs
|
||||
service <- mapM getClientService serviceKeys
|
||||
@@ -831,10 +836,12 @@ smpClientHandshake c ks_ keyHash@(C.KeyHash kh) vRange proxyServer serviceKeys_
|
||||
Nothing -> throwE TEVersion
|
||||
where
|
||||
th@THandle {params = THandleParams {sessionId}} = smpTHandle c
|
||||
mkClientService :: (ServiceCredentials, C.KeyPairEd25519) -> SMPClientHandshakeService
|
||||
mkClientService (ServiceCredentials {serviceRole, serviceCreds, serviceSignKey}, (k, _)) =
|
||||
let sk = C.signX509 serviceSignKey $ C.publicToX509 k
|
||||
in SMPClientHandshakeService {serviceRole, serviceCertKey = CertChainPubKey (fst serviceCreds) sk}
|
||||
mkClientService :: VersionSMP -> (ServiceCredentials, C.KeyPairEd25519) -> Maybe SMPClientHandshakeService
|
||||
mkClientService v (ServiceCredentials {serviceRole, serviceCreds, serviceSignKey}, (k, _))
|
||||
| serviceRole == SRMessaging && v < rcvServiceSMPVersion = Nothing
|
||||
| otherwise =
|
||||
let sk = C.signX509 serviceSignKey $ C.publicToX509 k
|
||||
in Just SMPClientHandshakeService {serviceRole, serviceCertKey = CertChainPubKey (fst serviceCreds) sk}
|
||||
getClientService :: (ServiceCredentials, C.KeyPairEd25519) -> ExceptT TransportError IO THClientService
|
||||
getClientService (ServiceCredentials {serviceRole, serviceCertHash}, (_, pk)) =
|
||||
getHandshake th >>= \case
|
||||
|
||||
@@ -85,7 +85,7 @@ import Simplex.Messaging.Agent hiding (acceptContact, createConnection, deleteCo
|
||||
import qualified Simplex.Messaging.Agent as A
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), ServerQueueInfo (..), UserNetworkInfo (..), UserNetworkType (..), waitForUserNetwork)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), Env (..), InitialAgentServers (..), createAgentStore)
|
||||
import Simplex.Messaging.Agent.Protocol hiding (CON, CONF, INFO, REQ, SENT, INV, JOINED)
|
||||
import Simplex.Messaging.Agent.Protocol hiding (CON, CONF, INFO, REQ, SENT)
|
||||
import qualified Simplex.Messaging.Agent.Protocol as A
|
||||
import Simplex.Messaging.Agent.Store (Connection' (..), SomeConn' (..), StoredRcvQueue (..))
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (getConn)
|
||||
@@ -219,12 +219,6 @@ pattern SENT msgId = A.SENT msgId Nothing
|
||||
pattern Rcvd :: AgentMsgId -> AEvent 'AEConn
|
||||
pattern Rcvd agentMsgId <- RCVD MsgMeta {integrity = MsgOk} [MsgReceipt {agentMsgId, msgRcptStatus = MROk}]
|
||||
|
||||
pattern INV :: AConnectionRequestUri -> AEvent 'AEConn
|
||||
pattern INV cReq = A.INV cReq Nothing
|
||||
|
||||
pattern JOINED :: SndQueueSecured -> AEvent 'AEConn
|
||||
pattern JOINED sndSecure = A.JOINED sndSecure Nothing
|
||||
|
||||
smpCfgVPrev :: ProtocolClientConfig SMPVersion
|
||||
smpCfgVPrev = (smpCfg agentCfg) {serverVRange = prevRange $ serverVRange $ smpCfg agentCfg}
|
||||
|
||||
@@ -282,16 +276,16 @@ inAnyOrder g rs = withFrozenCallStack $ do
|
||||
|
||||
createConnection :: ConnectionModeI c => AgentClient -> UserId -> Bool -> SConnectionMode c -> Maybe CRClientData -> SubscriptionMode -> AE (ConnId, ConnectionRequestUri c)
|
||||
createConnection c userId enableNtfs cMode clientData subMode = do
|
||||
(connId, (CCLink cReq _, Nothing)) <- A.createConnection c NRMInteractive userId enableNtfs True cMode Nothing clientData IKPQOn subMode
|
||||
(connId, CCLink cReq _) <- A.createConnection c NRMInteractive userId enableNtfs True cMode Nothing clientData IKPQOn subMode
|
||||
pure (connId, cReq)
|
||||
|
||||
joinConnection :: AgentClient -> UserId -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> AE (ConnId, SndQueueSecured)
|
||||
joinConnection c userId enableNtfs cReq connInfo subMode = do
|
||||
connId <- A.prepareConnectionToJoin c userId enableNtfs cReq PQSupportOn
|
||||
(sndSecure, Nothing) <- A.joinConnection c NRMInteractive userId connId enableNtfs cReq connInfo PQSupportOn subMode
|
||||
sndSecure <- A.joinConnection c NRMInteractive userId connId enableNtfs cReq connInfo PQSupportOn subMode
|
||||
pure (connId, sndSecure)
|
||||
|
||||
acceptContact :: AgentClient -> UserId -> ConnId -> Bool -> ConfirmationId -> ConnInfo -> PQSupport -> SubscriptionMode -> AE (SndQueueSecured, Maybe ClientServiceId)
|
||||
acceptContact :: AgentClient -> UserId -> ConnId -> Bool -> ConfirmationId -> ConnInfo -> PQSupport -> SubscriptionMode -> AE SndQueueSecured
|
||||
acceptContact c = A.acceptContact c NRMInteractive
|
||||
|
||||
subscribeConnection :: AgentClient -> ConnId -> AE ()
|
||||
@@ -708,9 +702,9 @@ runAgentClientTest pqSupport sqSecured viaProxy alice bob baseId =
|
||||
runAgentClientTestPQ :: HasCallStack => SndQueueSecured -> Bool -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
|
||||
runAgentClientTestPQ sqSecured viaProxy (alice, aPQ) (bob, bPQ) baseId =
|
||||
runRight_ $ do
|
||||
(bobId, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing aPQ SMSubscribe
|
||||
(bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing aPQ SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo bPQ
|
||||
(sqSecured', Nothing) <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" bPQ SMSubscribe
|
||||
sqSecured' <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" bPQ SMSubscribe
|
||||
liftIO $ sqSecured' `shouldBe` sqSecured
|
||||
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` CR.connPQEncryption aPQ
|
||||
@@ -910,14 +904,14 @@ runAgentClientContactTest pqSupport sqSecured viaProxy alice bob baseId =
|
||||
runAgentClientContactTestPQ :: HasCallStack => SndQueueSecured -> Bool -> PQSupport -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
|
||||
runAgentClientContactTestPQ sqSecured viaProxy reqPQSupport (alice, aPQ) (bob, bPQ) baseId =
|
||||
runRight_ $ do
|
||||
(_, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing aPQ SMSubscribe
|
||||
(_, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing aPQ SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo bPQ
|
||||
(sqSecuredJoin, Nothing) <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" bPQ SMSubscribe
|
||||
sqSecuredJoin <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" bPQ SMSubscribe
|
||||
liftIO $ sqSecuredJoin `shouldBe` False -- joining via contact address connection
|
||||
("", _, A.REQ invId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` reqPQSupport
|
||||
bobId <- A.prepareConnectionToAccept alice 1 True invId (CR.connPQEncryption aPQ)
|
||||
(sqSecured', Nothing) <- acceptContact alice 1 bobId True invId "alice's connInfo" (CR.connPQEncryption aPQ) SMSubscribe
|
||||
sqSecured' <- acceptContact alice 1 bobId True invId "alice's connInfo" (CR.connPQEncryption aPQ) SMSubscribe
|
||||
liftIO $ sqSecured' `shouldBe` sqSecured
|
||||
("", _, A.CONF confId pqSup'' _ "alice's connInfo") <- get bob
|
||||
liftIO $ pqSup'' `shouldBe` bPQ
|
||||
@@ -954,7 +948,7 @@ runAgentClientContactTestPQ sqSecured viaProxy reqPQSupport (alice, aPQ) (bob, b
|
||||
|
||||
runAgentClientContactTestPQ3 :: HasCallStack => Bool -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
|
||||
runAgentClientContactTestPQ3 viaProxy (alice, aPQ) (bob, bPQ) (tom, tPQ) baseId = runRight_ $ do
|
||||
(_, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing aPQ SMSubscribe
|
||||
(_, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing aPQ SMSubscribe
|
||||
(bAliceId, bobId, abPQEnc) <- connectViaContact bob bPQ qInfo
|
||||
sentMessages abPQEnc alice bobId bob bAliceId
|
||||
(tAliceId, tomId, atPQEnc) <- connectViaContact tom tPQ qInfo
|
||||
@@ -963,12 +957,12 @@ runAgentClientContactTestPQ3 viaProxy (alice, aPQ) (bob, bPQ) (tom, tPQ) baseId
|
||||
msgId = subtract baseId . fst
|
||||
connectViaContact b pq qInfo = do
|
||||
aId <- A.prepareConnectionToJoin b 1 True qInfo pq
|
||||
(sqSecuredJoin, Nothing) <- A.joinConnection b NRMInteractive 1 aId True qInfo "bob's connInfo" pq SMSubscribe
|
||||
sqSecuredJoin <- A.joinConnection b NRMInteractive 1 aId True qInfo "bob's connInfo" pq SMSubscribe
|
||||
liftIO $ sqSecuredJoin `shouldBe` False -- joining via contact address connection
|
||||
("", _, A.REQ invId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` PQSupportOn
|
||||
bId <- A.prepareConnectionToAccept alice 1 True invId (CR.connPQEncryption aPQ)
|
||||
(sqSecuredAccept, Nothing) <- acceptContact alice 1 bId True invId "alice's connInfo" (CR.connPQEncryption aPQ) SMSubscribe
|
||||
sqSecuredAccept <- acceptContact alice 1 bId True invId "alice's connInfo" (CR.connPQEncryption aPQ) SMSubscribe
|
||||
liftIO $ sqSecuredAccept `shouldBe` False -- agent cfg is v8
|
||||
("", _, A.CONF confId pqSup'' _ "alice's connInfo") <- get b
|
||||
liftIO $ pqSup'' `shouldBe` pq
|
||||
@@ -1007,9 +1001,9 @@ noMessages_ ingoreQCONT c err = tryGet `shouldReturn` ()
|
||||
testRejectContactRequest :: HasCallStack => IO ()
|
||||
testRejectContactRequest =
|
||||
withAgentClients2 $ \alice bob -> runRight_ $ do
|
||||
(_addrConnId, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing IKPQOn SMSubscribe
|
||||
(_addrConnId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing IKPQOn SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo PQSupportOn
|
||||
(sqSecured, Nothing) <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
sqSecured <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sqSecured `shouldBe` False -- joining via contact address connection
|
||||
("", _, A.REQ invId PQSupportOn _ "bob's connInfo") <- get alice
|
||||
rejectContact alice invId
|
||||
@@ -1022,7 +1016,7 @@ testUpdateConnectionUserId =
|
||||
newUserId <- createUser alice [noAuthSrvCfg testSMPServer] [noAuthSrvCfg testXFTPServer]
|
||||
_ <- changeConnectionUser alice 1 connId newUserId
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo PQSupportOn
|
||||
(sqSecured', Nothing) <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
sqSecured' <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sqSecured' `shouldBe` True
|
||||
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` PQSupportOn
|
||||
@@ -1206,7 +1200,7 @@ testInvitationErrors ps restart = do
|
||||
threadDelay 200000
|
||||
let loopConfirm n =
|
||||
runExceptT (A.joinConnection b' NRMInteractive 1 aId True cReq "bob's connInfo" PQSupportOn SMSubscribe) >>= \case
|
||||
Right (True, Nothing) -> pure n
|
||||
Right True -> pure n
|
||||
Right r -> error $ "unexpected result " <> show r
|
||||
Left _ -> putStrLn "retrying confirm" >> threadDelay 200000 >> loopConfirm (n + 1)
|
||||
n <- loopConfirm 1
|
||||
@@ -1268,7 +1262,7 @@ testContactErrors ps restart = do
|
||||
let loopSend = do
|
||||
-- sends the invitation to testPort
|
||||
runExceptT (A.joinConnection b'' NRMInteractive 1 aId True cReq "bob's connInfo" PQSupportOn SMSubscribe) >>= \case
|
||||
Right (False, Nothing) -> pure ()
|
||||
Right False -> pure ()
|
||||
Right r -> error $ "unexpected result " <> show r
|
||||
Left _ -> putStrLn "retrying send" >> threadDelay 200000 >> loopSend
|
||||
loopSend
|
||||
@@ -1297,7 +1291,7 @@ testContactErrors ps restart = do
|
||||
("", "", UP _ [_]) <- nGet b''
|
||||
let loopConfirm n =
|
||||
runExceptT (acceptContact a' 1 bId True invId "alice's connInfo" PQSupportOn SMSubscribe) >>= \case
|
||||
Right (True, Nothing) -> pure n
|
||||
Right True -> pure n
|
||||
Right r -> error $ "unexpected result " <> show r
|
||||
Left _ -> putStrLn "retrying accept confirm" >> threadDelay 200000 >> loopConfirm (n + 1)
|
||||
n <- loopConfirm 1
|
||||
@@ -1334,7 +1328,7 @@ testInvitationShortLink viaProxy a b =
|
||||
withAgent 3 agentCfg initAgentServers testDB3 $ \c -> do
|
||||
let userData = UserLinkData "some user data"
|
||||
newLinkData = UserInvLinkData userData
|
||||
(bId, (CCLink connReq (Just shortLink), Nothing)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKUsePQ SMSubscribe
|
||||
(bId, CCLink connReq (Just shortLink)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKUsePQ SMSubscribe
|
||||
(connReq', connData') <- runRight $ getConnShortLink b 1 shortLink
|
||||
strDecode (strEncode shortLink) `shouldBe` Right shortLink
|
||||
connReq' `shouldBe` connReq
|
||||
@@ -1356,7 +1350,7 @@ testInvitationShortLink viaProxy a b =
|
||||
testJoinConn_ :: Bool -> Bool -> AgentClient -> ConnId -> AgentClient -> ConnectionRequestUri c -> ExceptT AgentErrorType IO ()
|
||||
testJoinConn_ viaProxy sndSecure a bId b connReq = do
|
||||
aId <- A.prepareConnectionToJoin b 1 True connReq PQSupportOn
|
||||
(sndSecure', Nothing) <- A.joinConnection b NRMInteractive 1 aId True connReq "bob's connInfo" PQSupportOn SMSubscribe
|
||||
sndSecure' <- A.joinConnection b NRMInteractive 1 aId True connReq "bob's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sndSecure' `shouldBe` sndSecure
|
||||
("", _, CONF confId _ "bob's connInfo") <- get a
|
||||
allowConnection a bId confId "alice's connInfo"
|
||||
@@ -1370,14 +1364,14 @@ testInvitationShortLinkPrev viaProxy sndSecure a b = runRight_ $ do
|
||||
let userData = UserLinkData "some user data"
|
||||
newLinkData = UserInvLinkData userData
|
||||
-- can't create short link with previous version
|
||||
(bId, (CCLink connReq Nothing, Nothing)) <- A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKPQOn SMSubscribe
|
||||
(bId, CCLink connReq Nothing) <- A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKPQOn SMSubscribe
|
||||
testJoinConn_ viaProxy sndSecure a bId b connReq
|
||||
|
||||
testInvitationShortLinkAsync :: HasCallStack => Bool -> AgentClient -> AgentClient -> IO ()
|
||||
testInvitationShortLinkAsync viaProxy a b = do
|
||||
let userData = UserLinkData "some user data"
|
||||
newLinkData = UserInvLinkData userData
|
||||
(bId, (CCLink connReq (Just shortLink), Nothing)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKUsePQ SMSubscribe
|
||||
(bId, CCLink connReq (Just shortLink)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKUsePQ SMSubscribe
|
||||
(connReq', connData') <- runRight $ getConnShortLink b 1 shortLink
|
||||
strDecode (strEncode shortLink) `shouldBe` Right shortLink
|
||||
connReq' `shouldBe` connReq
|
||||
@@ -1404,7 +1398,7 @@ testContactShortLink viaProxy a b =
|
||||
let userData = UserLinkData "some user data"
|
||||
userCtData = UserContactData {direct = True, owners = [], relays = [], userData}
|
||||
newLinkData = UserContactLinkData userCtData
|
||||
(contactId, (CCLink connReq0 (Just shortLink), Nothing)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMContact (Just newLinkData) Nothing CR.IKPQOn SMSubscribe
|
||||
(contactId, CCLink connReq0 (Just shortLink)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMContact (Just newLinkData) Nothing CR.IKPQOn SMSubscribe
|
||||
Right connReq <- pure $ smpDecode (smpEncode connReq0)
|
||||
(connReq', ContactLinkData _ userCtData') <- runRight $ getConnShortLink b 1 shortLink
|
||||
strDecode (strEncode shortLink) `shouldBe` Right shortLink
|
||||
@@ -1423,7 +1417,7 @@ testContactShortLink viaProxy a b =
|
||||
liftIO $ sndSecure `shouldBe` False
|
||||
("", _, REQ invId _ "bob's connInfo") <- get a
|
||||
bId <- A.prepareConnectionToAccept a 1 True invId PQSupportOn
|
||||
(sndSecure', Nothing) <- acceptContact a 1 bId True invId "alice's connInfo" PQSupportOn SMSubscribe
|
||||
sndSecure' <- acceptContact a 1 bId True invId "alice's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sndSecure' `shouldBe` True
|
||||
("", _, CONF confId _ "alice's connInfo") <- get b
|
||||
allowConnection b aId confId "bob's connInfo"
|
||||
@@ -1451,7 +1445,7 @@ testContactShortLink viaProxy a b =
|
||||
testAddContactShortLink :: HasCallStack => Bool -> AgentClient -> AgentClient -> IO ()
|
||||
testAddContactShortLink viaProxy a b =
|
||||
withAgent 3 agentCfg initAgentServers testDB3 $ \c -> do
|
||||
(contactId, (CCLink connReq0 Nothing, Nothing)) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMContact Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
(contactId, CCLink connReq0 Nothing) <- runRight $ A.createConnection a NRMInteractive 1 True True SCMContact Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
Right connReq <- pure $ smpDecode (smpEncode connReq0) --
|
||||
let userData = UserLinkData "some user data"
|
||||
userCtData = UserContactData {direct = True, owners = [], relays = [], userData}
|
||||
@@ -1474,7 +1468,7 @@ testAddContactShortLink viaProxy a b =
|
||||
liftIO $ sndSecure `shouldBe` False
|
||||
("", _, REQ invId _ "bob's connInfo") <- get a
|
||||
bId <- A.prepareConnectionToAccept a 1 True invId PQSupportOn
|
||||
(sndSecure', Nothing) <- acceptContact a 1 bId True invId "alice's connInfo" PQSupportOn SMSubscribe
|
||||
sndSecure' <- acceptContact a 1 bId True invId "alice's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sndSecure' `shouldBe` True
|
||||
("", _, CONF confId _ "alice's connInfo") <- get b
|
||||
allowConnection b aId confId "bob's connInfo"
|
||||
@@ -1496,7 +1490,7 @@ testInvitationShortLinkRestart :: HasCallStack => (ASrvTransport, AStoreType) ->
|
||||
testInvitationShortLinkRestart ps = withAgentClients2 $ \a b -> do
|
||||
let userData = UserLinkData "some user data"
|
||||
newLinkData = UserInvLinkData userData
|
||||
(bId, (CCLink connReq (Just shortLink), Nothing)) <- withSmpServer ps $
|
||||
(bId, CCLink connReq (Just shortLink)) <- withSmpServer ps $
|
||||
runRight $ A.createConnection a NRMInteractive 1 True True SCMInvitation (Just newLinkData) Nothing CR.IKUsePQ SMOnlyCreate
|
||||
withSmpServer ps $ do
|
||||
runRight_ $ subscribeConnection a bId
|
||||
@@ -1510,7 +1504,7 @@ testContactShortLinkRestart ps = withAgentClients2 $ \a b -> do
|
||||
let userData = UserLinkData "some user data"
|
||||
userCtData = UserContactData {direct = True, owners = [], relays = [], userData}
|
||||
newLinkData = UserContactLinkData userCtData
|
||||
(contactId, (CCLink connReq0 (Just shortLink), Nothing)) <- withSmpServer ps $
|
||||
(contactId, CCLink connReq0 (Just shortLink)) <- withSmpServer ps $
|
||||
runRight $ A.createConnection a NRMInteractive 1 True True SCMContact (Just newLinkData) Nothing CR.IKPQOn SMOnlyCreate
|
||||
Right connReq <- pure $ smpDecode (smpEncode connReq0)
|
||||
let updatedData = UserLinkData "updated user data"
|
||||
@@ -1534,7 +1528,7 @@ testAddContactShortLinkRestart ps = withAgentClients2 $ \a b -> do
|
||||
let userData = UserLinkData "some user data"
|
||||
userCtData = UserContactData {direct = True, owners = [], relays = [], userData}
|
||||
newLinkData = UserContactLinkData userCtData
|
||||
((contactId, (CCLink connReq0 Nothing, Nothing)), shortLink) <- withSmpServer ps $ runRight $ do
|
||||
((contactId, CCLink connReq0 Nothing), shortLink) <- withSmpServer ps $ runRight $ do
|
||||
r@(contactId, _) <- A.createConnection a NRMInteractive 1 True True SCMContact Nothing Nothing CR.IKPQOn SMOnlyCreate
|
||||
(r,) <$> setConnShortLink a contactId SCMContact newLinkData Nothing
|
||||
Right connReq <- pure $ smpDecode (smpEncode connReq0)
|
||||
@@ -1556,7 +1550,7 @@ testAddContactShortLinkRestart ps = withAgentClients2 $ \a b -> do
|
||||
|
||||
testOldContactQueueShortLink :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
|
||||
testOldContactQueueShortLink ps@(_, msType) = withAgentClients2 $ \a b -> do
|
||||
(contactId, (CCLink connReq Nothing, Nothing)) <- withSmpServer ps $ runRight $
|
||||
(contactId, CCLink connReq Nothing) <- withSmpServer ps $ runRight $
|
||||
A.createConnection a NRMInteractive 1 True True SCMContact Nothing Nothing CR.IKPQOn SMOnlyCreate
|
||||
-- make it an "old" queue
|
||||
let updateStoreLog f = replaceSubstringInFile f " queue_mode=C" ""
|
||||
@@ -2301,9 +2295,9 @@ makeConnectionForUsers = makeConnectionForUsers_ PQSupportOn True
|
||||
|
||||
makeConnectionForUsers_ :: HasCallStack => PQSupport -> SndQueueSecured -> AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
|
||||
makeConnectionForUsers_ pqSupport sqSecured alice aliceUserId bob bobUserId = do
|
||||
(bobId, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive aliceUserId True True SCMInvitation Nothing Nothing (IKLinkPQ pqSupport) SMSubscribe
|
||||
(bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive aliceUserId True True SCMInvitation Nothing Nothing (IKLinkPQ pqSupport) SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob bobUserId True qInfo pqSupport
|
||||
(sqSecured', Nothing) <- A.joinConnection bob NRMInteractive bobUserId aliceId True qInfo "bob's connInfo" pqSupport SMSubscribe
|
||||
sqSecured' <- A.joinConnection bob NRMInteractive bobUserId aliceId True qInfo "bob's connInfo" pqSupport SMSubscribe
|
||||
liftIO $ sqSecured' `shouldBe` sqSecured
|
||||
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` pqSupport
|
||||
|
||||
@@ -227,7 +227,7 @@ rcvQueue1 =
|
||||
sndId = EntityId "2345",
|
||||
queueMode = Just QMMessaging,
|
||||
shortLink = Nothing,
|
||||
clientService = Nothing,
|
||||
rcvServiceAssoc = False,
|
||||
status = New,
|
||||
enableNtfs = True,
|
||||
clientNoticeId = Nothing,
|
||||
@@ -441,7 +441,7 @@ testUpgradeSndConnToDuplex =
|
||||
sndId = EntityId "4567",
|
||||
queueMode = Just QMMessaging,
|
||||
shortLink = Nothing,
|
||||
clientService = Nothing,
|
||||
rcvServiceAssoc = False,
|
||||
status = New,
|
||||
enableNtfs = True,
|
||||
clientNoticeId = Nothing,
|
||||
|
||||
@@ -64,6 +64,7 @@ initServers =
|
||||
ntf = [testNtfServer],
|
||||
xftp = userServers [testXFTPServer],
|
||||
netCfg = defaultNetworkConfig,
|
||||
useServices = M.empty,
|
||||
presetDomains = [],
|
||||
presetServers = []
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ initAgentServers =
|
||||
ntf = [testNtfServer],
|
||||
xftp = userServers [testXFTPServer],
|
||||
netCfg = defaultNetworkConfig {tcpTimeout = NetworkTimeout 500000 500000, tcpConnectTimeout = NetworkTimeout 500000 500000},
|
||||
useServices = M.empty,
|
||||
presetDomains = [],
|
||||
presetServers = []
|
||||
}
|
||||
|
||||
+29
-5
@@ -15,10 +15,14 @@
|
||||
|
||||
module SMPClient where
|
||||
|
||||
import Control.Monad
|
||||
import Control.Monad.Except (runExceptT)
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import qualified Data.X509 as X
|
||||
import qualified Data.X509.Validation as XV
|
||||
import Network.Socket
|
||||
import qualified Network.TLS as TLS
|
||||
import Simplex.Messaging.Agent.Store.Postgres.Options (DBOpts (..))
|
||||
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..))
|
||||
import Simplex.Messaging.Client (ProtocolClientConfig (..), chooseTransportHost, defaultNetworkConfig)
|
||||
@@ -33,6 +37,7 @@ import Simplex.Messaging.Server.QueueStore.Postgres.Config (PostgresStoreCfg (..
|
||||
import Simplex.Messaging.Transport
|
||||
import Simplex.Messaging.Transport.Client
|
||||
import Simplex.Messaging.Transport.Server
|
||||
import Simplex.Messaging.Transport.Shared (ChainCertificates (..), chainIdCaCerts)
|
||||
import Simplex.Messaging.Util (ifM)
|
||||
import Simplex.Messaging.Version
|
||||
import Simplex.Messaging.Version.Internal
|
||||
@@ -151,13 +156,26 @@ testSMPClient = testSMPClientVR supportedClientSMPRelayVRange
|
||||
testSMPClientVR :: Transport c => VersionRangeSMP -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
testSMPClientVR vr client = do
|
||||
Right useHost <- pure $ chooseTransportHost defaultNetworkConfig testHost
|
||||
testSMPClient_ useHost testPort vr client
|
||||
testSMPClient_ useHost testPort vr Nothing client
|
||||
|
||||
testSMPClient_ :: Transport c => TransportHost -> ServiceName -> VersionRangeSMP -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
testSMPClient_ host port vr client = do
|
||||
let tcConfig = defaultTransportClientConfig {clientALPN} :: TransportClientConfig
|
||||
testSMPServiceClient :: Transport c => (TLS.Credential, C.KeyPairEd25519) -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
testSMPServiceClient serviceCreds client = do
|
||||
Right useHost <- pure $ chooseTransportHost defaultNetworkConfig testHost
|
||||
testSMPClient_ useHost testPort supportedClientSMPRelayVRange (Just serviceCreds) client
|
||||
|
||||
testSMPClient_ :: Transport c => TransportHost -> ServiceName -> VersionRangeSMP -> Maybe (TLS.Credential, C.KeyPairEd25519) -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
testSMPClient_ host port vr serviceCreds_ client = do
|
||||
serviceAndKeys_ <- forM serviceCreds_ $ \(serviceCreds@(cc, pk), keys) -> do
|
||||
Right serviceSignKey <- pure $ C.x509ToPrivate' pk
|
||||
let idCert' = case chainIdCaCerts cc of
|
||||
CCSelf cert -> cert
|
||||
CCValid {idCert} -> idCert
|
||||
_ -> error "bad certificate"
|
||||
serviceCertHash = XV.getFingerprint idCert' X.HashSHA256
|
||||
pure (ServiceCredentials {serviceRole = SRMessaging, serviceCreds, serviceCertHash, serviceSignKey}, keys)
|
||||
let tcConfig = defaultTransportClientConfig {clientALPN, clientCredentials = fst <$> serviceCreds_} :: TransportClientConfig
|
||||
runTransportClient tcConfig Nothing host port (Just testKeyHash) $ \h ->
|
||||
runExceptT (smpClientHandshake h Nothing testKeyHash vr False Nothing) >>= \case
|
||||
runExceptT (smpClientHandshake h Nothing testKeyHash vr False serviceAndKeys_) >>= \case
|
||||
Right th -> client th
|
||||
Left e -> error $ show e
|
||||
where
|
||||
@@ -165,6 +183,12 @@ testSMPClient_ host port vr client = do
|
||||
| authCmdsSMPVersion `isCompatible` vr = Just alpnSupportedSMPHandshakes
|
||||
| otherwise = Nothing
|
||||
|
||||
runSMPClient :: Transport c => TProxy c 'TServer -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
runSMPClient _ test' = testSMPClient test'
|
||||
|
||||
runSMPServiceClient :: Transport c => TProxy c 'TServer -> (TLS.Credential, C.KeyPairEd25519) -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
runSMPServiceClient _ serviceCreds test' = testSMPServiceClient serviceCreds test'
|
||||
|
||||
testNtfServiceClient :: Transport c => TProxy c 'TServer -> C.KeyPairEd25519 -> (THandleSMP c 'TClient -> IO a) -> IO a
|
||||
testNtfServiceClient _ keys client = do
|
||||
tlsNtfServerCreds <- loadServerCredential ntfTestServerCredentials
|
||||
|
||||
@@ -224,9 +224,9 @@ agentDeliverMessageViaProxy :: (C.AlgorithmI a, C.AuthAlgorithm a) => (NonEmpty
|
||||
agentDeliverMessageViaProxy aTestCfg@(aSrvs, _, aViaProxy) bTestCfg@(bSrvs, _, bViaProxy) alg msg1 msg2 baseId =
|
||||
withAgent 1 aCfg (servers aTestCfg) testDB $ \alice ->
|
||||
withAgent 2 aCfg (servers bTestCfg) testDB2 $ \bob -> runRight_ $ do
|
||||
(bobId, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
(bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo PQSupportOn
|
||||
(sqSecured, Nothing) <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
sqSecured <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sqSecured `shouldBe` True
|
||||
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` PQSupportOn
|
||||
@@ -280,9 +280,9 @@ agentDeliverMessagesViaProxyConc agentServers msgs =
|
||||
-- agent connections have to be set up in advance
|
||||
-- otherwise the CONF messages would get mixed with MSG
|
||||
prePair alice bob = do
|
||||
(bobId, (CCLink qInfo Nothing, Nothing)) <- runExceptT' $ A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
(bobId, CCLink qInfo Nothing) <- runExceptT' $ A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
aliceId <- runExceptT' $ A.prepareConnectionToJoin bob 1 True qInfo PQSupportOn
|
||||
(sqSecured, Nothing) <- runExceptT' $ A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
sqSecured <- runExceptT' $ A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sqSecured `shouldBe` True
|
||||
confId <-
|
||||
get alice >>= \case
|
||||
@@ -331,7 +331,7 @@ agentViaProxyVersionError =
|
||||
withAgent 1 agentCfg (servers [SMPServer testHost testPort testKeyHash]) testDB $ \alice -> do
|
||||
Left (A.BROKER _ (TRANSPORT TEVersion)) <-
|
||||
withAgent 2 agentCfg (servers [SMPServer testHost2 testPort2 testKeyHash]) testDB2 $ \bob -> runExceptT $ do
|
||||
(_bobId, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
(_bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo PQSupportOn
|
||||
A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
pure ()
|
||||
@@ -351,9 +351,9 @@ agentViaProxyRetryOffline = do
|
||||
let pqEnc = CR.PQEncOn
|
||||
withServer $ \_ -> do
|
||||
(aliceId, bobId) <- withServer2 $ \_ -> runRight $ do
|
||||
(bobId, (CCLink qInfo Nothing, Nothing)) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
(bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing CR.IKPQOn SMSubscribe
|
||||
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo PQSupportOn
|
||||
(sqSecured, Nothing) <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
sqSecured <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sqSecured `shouldBe` True
|
||||
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
|
||||
liftIO $ pqSup' `shouldBe` PQSupportOn
|
||||
@@ -434,14 +434,14 @@ agentViaProxyRetryNoSession = do
|
||||
testNoProxy :: AStoreType -> IO ()
|
||||
testNoProxy msType = do
|
||||
withSmpServerConfigOn (transport @TLS) (cfgMS msType) testPort2 $ \_ -> do
|
||||
testSMPClient_ "127.0.0.1" testPort2 proxyVRangeV8 $ \(th :: THandleSMP TLS 'TClient) -> do
|
||||
testSMPClient_ "127.0.0.1" testPort2 proxyVRangeV8 Nothing $ \(th :: THandleSMP TLS 'TClient) -> do
|
||||
(_, _, reply) <- sendRecv th (Nothing, "0", NoEntity, SMP.PRXY testSMPServer Nothing)
|
||||
reply `shouldBe` Right (SMP.ERR $ SMP.PROXY SMP.BASIC_AUTH)
|
||||
|
||||
testProxyAuth :: AStoreType -> IO ()
|
||||
testProxyAuth msType = do
|
||||
withSmpServerConfigOn (transport @TLS) proxyCfgAuth testPort $ \_ -> do
|
||||
testSMPClient_ "127.0.0.1" testPort proxyVRangeV8 $ \(th :: THandleSMP TLS 'TClient) -> do
|
||||
testSMPClient_ "127.0.0.1" testPort proxyVRangeV8 Nothing $ \(th :: THandleSMP TLS 'TClient) -> do
|
||||
(_, _, reply) <- sendRecv th (Nothing, "0", NoEntity, SMP.PRXY testSMPServer2 $ Just "wrong")
|
||||
reply `shouldBe` Right (SMP.ERR $ SMP.PROXY SMP.BASIC_AUTH)
|
||||
where
|
||||
|
||||
+212
-5
@@ -29,9 +29,11 @@ import Data.Bifunctor (first)
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Foldable (foldrM)
|
||||
import Data.Hashable (hash)
|
||||
import qualified Data.IntSet as IS
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.Maybe (catMaybes)
|
||||
import Data.String (IsString (..))
|
||||
import Data.Type.Equality
|
||||
import qualified Data.X509.Validation as XV
|
||||
@@ -50,6 +52,7 @@ import Simplex.Messaging.Server.MsgStore.Types (MsgStoreClass (..), QSType (..),
|
||||
import Simplex.Messaging.Server.Stats (PeriodStatsData (..), ServerStatsData (..))
|
||||
import Simplex.Messaging.Server.StoreLog (StoreLogRecord (..), closeStoreLog)
|
||||
import Simplex.Messaging.Transport
|
||||
import Simplex.Messaging.Transport.Credentials
|
||||
import Simplex.Messaging.Util (whenM)
|
||||
import Simplex.Messaging.Version (mkVersionRange)
|
||||
import System.Directory (doesDirectoryExist, doesFileExist, removeDirectoryRecursive, removeFile)
|
||||
@@ -84,6 +87,9 @@ serverTests = do
|
||||
describe "GET & SUB commands" testGetSubCommands
|
||||
describe "Exceeding queue quota" testExceedQueueQuota
|
||||
describe "Concurrent sending and delivery" testConcurrentSendDelivery
|
||||
describe "Service message subscriptions" $ do
|
||||
testServiceDeliverSubscribe
|
||||
testServiceUpgradeAndDowngrade
|
||||
describe "Store log" testWithStoreLog
|
||||
describe "Restore messages" testRestoreMessages
|
||||
describe "Restore messages (old / v2)" testRestoreExpireMessages
|
||||
@@ -111,6 +117,9 @@ pattern New rPub dhPub = NEW (NewQueueReq rPub dhPub Nothing SMSubscribe (Just (
|
||||
pattern Ids :: RecipientId -> SenderId -> RcvPublicDhKey -> BrokerMsg
|
||||
pattern Ids rId sId srvDh <- IDS (QIK rId sId srvDh _sndSecure _linkId Nothing Nothing)
|
||||
|
||||
pattern Ids_ :: RecipientId -> SenderId -> RcvPublicDhKey -> ServiceId -> BrokerMsg
|
||||
pattern Ids_ rId sId srvDh serviceId <- IDS (QIK rId sId srvDh _sndSecure _linkId (Just serviceId) Nothing)
|
||||
|
||||
pattern Msg :: MsgId -> MsgBody -> BrokerMsg
|
||||
pattern Msg msgId body <- MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body}
|
||||
|
||||
@@ -135,11 +144,21 @@ serviceSignSendRecv h pk serviceKey t = do
|
||||
[r] <- signSendRecv_ h pk (Just serviceKey) t
|
||||
pure r
|
||||
|
||||
serviceSignSendRecv2 :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO (Transmission (Either ErrorType BrokerMsg), Transmission (Either ErrorType BrokerMsg))
|
||||
serviceSignSendRecv2 h pk serviceKey t = do
|
||||
[r1, r2] <- signSendRecv_ h pk (Just serviceKey) t
|
||||
pure (r1, r2)
|
||||
|
||||
signSendRecv_ :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> Maybe C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO (NonEmpty (Transmission (Either ErrorType BrokerMsg)))
|
||||
signSendRecv_ h@THandle {params} (C.APrivateAuthKey a pk) serviceKey_ (corrId, qId, cmd) = do
|
||||
signSendRecv_ h pk serviceKey_ t = do
|
||||
signSend_ h pk serviceKey_ t
|
||||
tGetClient h
|
||||
|
||||
signSend_ :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> Maybe C.PrivateKeyEd25519 -> (ByteString, EntityId, Command p) -> IO ()
|
||||
signSend_ h@THandle {params} (C.APrivateAuthKey a pk) serviceKey_ (corrId, qId, cmd) = do
|
||||
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
|
||||
Right () <- tPut1 h (authorize tForAuth, tToSend)
|
||||
liftIO $ tGetClient h
|
||||
pure ()
|
||||
where
|
||||
authorize t = (,(`C.sign'` t) <$> serviceKey_) <$> case a of
|
||||
C.SEd25519 -> Just . TASignature . C.ASignature C.SEd25519 $ C.sign' pk t'
|
||||
@@ -660,6 +679,194 @@ testConcurrentSendDelivery =
|
||||
Resp "4" _ OK <- signSendRecv rh rKey ("4", rId, ACK mId2)
|
||||
pure ()
|
||||
|
||||
testServiceDeliverSubscribe :: SpecWith (ASrvTransport, AStoreType)
|
||||
testServiceDeliverSubscribe =
|
||||
it "should create queue as service and subscribe with SUBS after reconnect" $ \(at@(ATransport t), msType) -> do
|
||||
g <- C.newRandom
|
||||
creds <- genCredentials g Nothing (0, 2400) "localhost"
|
||||
let (_fp, tlsCred) = tlsCredentials [creds]
|
||||
serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
|
||||
let aServicePK = C.APrivateAuthKey C.SEd25519 servicePK
|
||||
withSmpServerConfigOn at (cfgMS msType) testPort $ \_ -> runSMPClient t $ \h -> do
|
||||
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
||||
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
|
||||
(rId, sId, dec, serviceId) <- runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
|
||||
Resp "1" NoEntity (ERR SERVICE) <- signSendRecv sh rKey ("1", NoEntity, New rPub dhPub)
|
||||
Resp "2" NoEntity (Ids_ rId sId srvDh serviceId) <- serviceSignSendRecv sh rKey servicePK ("2", NoEntity, New rPub dhPub)
|
||||
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
||||
Resp "3" sId' OK <- signSendRecv h sKey ("3", sId, SKEY sPub)
|
||||
sId' `shouldBe` sId
|
||||
Resp "4" _ OK <- signSendRecv h sKey ("4", sId, _SEND "hello")
|
||||
Resp "5" _ OK <- signSendRecv h sKey ("5", sId, _SEND "hello 2")
|
||||
Resp "" rId' (Msg mId1 msg1) <- tGet1 sh
|
||||
rId' `shouldBe` rId
|
||||
dec mId1 msg1 `shouldBe` Right "hello"
|
||||
-- ACK doesn't need service signature
|
||||
Resp "6" _ (Msg mId2 msg2) <- signSendRecv sh rKey ("6", rId, ACK mId1)
|
||||
dec mId2 msg2 `shouldBe` Right "hello 2"
|
||||
Resp "7" _ (ERR NO_MSG) <- signSendRecv sh rKey ("7", rId, ACK mId1)
|
||||
Resp "8" _ OK <- signSendRecv sh rKey ("8", rId, ACK mId2)
|
||||
Resp "9" _ OK <- signSendRecv h sKey ("9", sId, _SEND "hello 3")
|
||||
pure (rId, sId, dec, serviceId)
|
||||
|
||||
runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
|
||||
Resp "10" NoEntity (ERR (CMD NO_AUTH)) <- signSendRecv sh aServicePK ("10", NoEntity, SUBS)
|
||||
signSend_ sh aServicePK Nothing ("11", serviceId, SUBS)
|
||||
[mId3] <-
|
||||
fmap catMaybes $
|
||||
receiveInAnyOrder -- race between SOKS and MSG, clients can handle it
|
||||
sh
|
||||
[ \case
|
||||
Resp "11" serviceId' (SOKS n _) -> do
|
||||
n `shouldBe` 1
|
||||
serviceId' `shouldBe` serviceId
|
||||
pure $ Just Nothing
|
||||
_ -> pure Nothing,
|
||||
\case
|
||||
Resp "" rId'' (Msg mId3 msg3) -> do
|
||||
rId'' `shouldBe` rId
|
||||
dec mId3 msg3 `shouldBe` Right "hello 3"
|
||||
pure $ Just $ Just mId3
|
||||
_ -> pure Nothing
|
||||
]
|
||||
Resp "" NoEntity SALL <- tGet1 sh
|
||||
Resp "12" _ OK <- signSendRecv sh rKey ("12", rId, ACK mId3)
|
||||
Resp "14" _ OK <- signSendRecv h sKey ("14", sId, _SEND "hello 4")
|
||||
Resp "" _ (Msg mId4 msg4) <- tGet1 sh
|
||||
dec mId4 msg4 `shouldBe` Right "hello 4"
|
||||
Resp "15" _ OK <- signSendRecv sh rKey ("15", rId, ACK mId4)
|
||||
pure ()
|
||||
|
||||
testServiceUpgradeAndDowngrade :: SpecWith (ASrvTransport, AStoreType)
|
||||
testServiceUpgradeAndDowngrade =
|
||||
it "should create queue as client and switch to service and back" $ \(at@(ATransport t), msType) -> do
|
||||
g <- C.newRandom
|
||||
creds <- genCredentials g Nothing (0, 2400) "localhost"
|
||||
let (_fp, tlsCred) = tlsCredentials [creds]
|
||||
serviceKeys@(_, servicePK) <- atomically $ C.generateKeyPair g
|
||||
let aServicePK = C.APrivateAuthKey C.SEd25519 servicePK
|
||||
withSmpServerConfigOn at (cfgMS msType) testPort $ \_ -> runSMPClient t $ \h -> do
|
||||
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
(dhPub, dhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
||||
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
(rPub2, rKey2) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
(dhPub2, dhPriv2 :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
||||
(sPub2, sKey2) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
(rPub3, rKey3) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
(dhPub3, dhPriv3 :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
|
||||
(sPub3, sKey3) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
|
||||
|
||||
(rId, sId, dec) <- runSMPClient t $ \sh -> do
|
||||
Resp "1" NoEntity (Ids rId sId srvDh) <- signSendRecv sh rKey ("1", NoEntity, New rPub dhPub)
|
||||
let dec = decryptMsgV3 $ C.dh' srvDh dhPriv
|
||||
Resp "2" sId' OK <- signSendRecv h sKey ("2", sId, SKEY sPub)
|
||||
sId' `shouldBe` sId
|
||||
Resp "3" _ OK <- signSendRecv h sKey ("3", sId, _SEND "hello")
|
||||
Resp "" rId' (Msg mId1 msg1) <- tGet1 sh
|
||||
rId' `shouldBe` rId
|
||||
dec mId1 msg1 `shouldBe` Right "hello"
|
||||
Resp "4" _ OK <- signSendRecv sh rKey ("4", rId, ACK mId1)
|
||||
Resp "5" _ OK <- signSendRecv h sKey ("5", sId, _SEND "hello 2")
|
||||
pure (rId, sId, dec)
|
||||
|
||||
-- split to prevent message delivery
|
||||
(rId2, sId2, dec2) <- runSMPClient t $ \sh -> do
|
||||
Resp "6" NoEntity (Ids rId2 sId2 srvDh2) <- signSendRecv sh rKey2 ("6", NoEntity, New rPub2 dhPub2)
|
||||
let dec2 = decryptMsgV3 $ C.dh' srvDh2 dhPriv2
|
||||
Resp "7" sId2' OK <- signSendRecv h sKey2 ("7", sId2, SKEY sPub2)
|
||||
sId2' `shouldBe` sId2
|
||||
pure (rId2, sId2, dec2)
|
||||
|
||||
(rId3, _sId3, _dec3) <- runSMPClient t $ \sh -> do
|
||||
Resp "6" NoEntity (Ids rId3 sId3 srvDh3) <- signSendRecv sh rKey3 ("6", NoEntity, New rPub3 dhPub3)
|
||||
let dec3 = decryptMsgV3 $ C.dh' srvDh3 dhPriv3
|
||||
Resp "7" sId3' OK <- signSendRecv h sKey3 ("7", sId3, SKEY sPub3)
|
||||
sId3' `shouldBe` sId3
|
||||
pure (rId3, sId3, dec3)
|
||||
|
||||
serviceId <- runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
|
||||
Resp "8" _ (ERR SERVICE) <- signSendRecv sh rKey ("8", rId, SUB)
|
||||
(Resp "9" rId' (SOK (Just serviceId)), Resp "" rId'' (Msg mId2 msg2)) <- serviceSignSendRecv2 sh rKey servicePK ("9", rId, SUB)
|
||||
rId' `shouldBe` rId
|
||||
rId'' `shouldBe` rId
|
||||
dec mId2 msg2 `shouldBe` Right "hello 2"
|
||||
(Resp "10" rId2' (SOK (Just serviceId'))) <- serviceSignSendRecv sh rKey2 servicePK ("10", rId2, SUB)
|
||||
rId2' `shouldBe` rId2
|
||||
serviceId' `shouldBe` serviceId
|
||||
Resp "10.1" _ OK <- signSendRecv sh rKey ("10.1", rId, ACK mId2)
|
||||
(Resp "10.2" rId3' (SOK (Just serviceId''))) <- serviceSignSendRecv sh rKey3 servicePK ("10.2", rId3, SUB)
|
||||
rId3' `shouldBe` rId3
|
||||
serviceId'' `shouldBe` serviceId
|
||||
pure serviceId
|
||||
|
||||
Resp "11" _ OK <- signSendRecv h sKey ("11", sId, _SEND "hello 3.1")
|
||||
Resp "12" _ OK <- signSendRecv h sKey2 ("12", sId2, _SEND "hello 3.2")
|
||||
|
||||
runSMPServiceClient t (tlsCred, serviceKeys) $ \sh -> do
|
||||
signSend_ sh aServicePK Nothing ("14", serviceId, SUBS)
|
||||
[(rKey3_1, rId3_1, mId3_1), (rKey3_2, rId3_2, mId3_2)] <-
|
||||
fmap catMaybes $
|
||||
receiveInAnyOrder -- race between SOKS and MSG, clients can handle it
|
||||
sh
|
||||
[ \case
|
||||
Resp "14" serviceId' (SOKS n _) -> do
|
||||
n `shouldBe` 3
|
||||
serviceId' `shouldBe` serviceId
|
||||
pure $ Just Nothing
|
||||
_ -> pure Nothing,
|
||||
\case
|
||||
Resp "" rId'' (Msg mId3 msg3) | rId'' == rId -> do
|
||||
dec mId3 msg3 `shouldBe` Right "hello 3.1"
|
||||
pure $ Just $ Just (rKey, rId, mId3)
|
||||
_ -> pure Nothing,
|
||||
\case
|
||||
Resp "" rId'' (Msg mId3 msg3) | rId'' == rId2 -> do
|
||||
dec2 mId3 msg3 `shouldBe` Right "hello 3.2"
|
||||
pure $ Just $ Just (rKey2, rId2, mId3)
|
||||
_ -> pure Nothing
|
||||
]
|
||||
Resp "" NoEntity SALL <- tGet1 sh
|
||||
Resp "15" _ OK <- signSendRecv sh rKey3_1 ("15", rId3_1, ACK mId3_1)
|
||||
Resp "16" _ OK <- signSendRecv sh rKey3_2 ("16", rId3_2, ACK mId3_2)
|
||||
pure ()
|
||||
|
||||
Resp "17" _ OK <- signSendRecv h sKey ("17", sId, _SEND "hello 4")
|
||||
|
||||
runSMPClient t $ \sh -> do
|
||||
Resp "18" _ (ERR SERVICE) <- signSendRecv sh aServicePK ("18", serviceId, SUBS)
|
||||
(Resp "19" rId' (SOK Nothing), Resp "" rId'' (Msg mId4 msg4)) <- signSendRecv2 sh rKey ("19", rId, SUB)
|
||||
rId' `shouldBe` rId
|
||||
rId'' `shouldBe` rId
|
||||
dec mId4 msg4 `shouldBe` Right "hello 4"
|
||||
Resp "20" _ OK <- signSendRecv sh rKey ("20", rId, ACK mId4)
|
||||
Resp "21" _ OK <- signSendRecv h sKey ("21", sId, _SEND "hello 5")
|
||||
Resp "" _ (Msg mId5 msg5) <- tGet1 sh
|
||||
dec mId5 msg5 `shouldBe` Right "hello 5"
|
||||
Resp "22" _ OK <- signSendRecv sh rKey ("22", rId, ACK mId5)
|
||||
|
||||
Resp "23" rId2' (SOK Nothing) <- signSendRecv sh rKey2 ("23", rId2, SUB)
|
||||
rId2' `shouldBe` rId2
|
||||
Resp "24" _ OK <- signSendRecv h sKey ("24", sId, _SEND "hello 6")
|
||||
Resp "" _ (Msg mId6 msg6) <- tGet1 sh
|
||||
dec mId6 msg6 `shouldBe` Right "hello 6"
|
||||
Resp "25" _ OK <- signSendRecv sh rKey ("25", rId, ACK mId6)
|
||||
pure ()
|
||||
|
||||
receiveInAnyOrder :: (HasCallStack, Transport c) => THandleSMP c 'TClient -> [(CorrId, EntityId, Either ErrorType BrokerMsg) -> IO (Maybe b)] -> IO [b]
|
||||
receiveInAnyOrder h = fmap reverse . go []
|
||||
where
|
||||
go rs [] = pure rs
|
||||
go rs ps = withFrozenCallStack $ do
|
||||
r <- 5000000 `timeout` tGet1 h >>= maybe (error "inAnyOrder timeout") pure
|
||||
(r_, ps') <- foldrM (choose r) (Nothing, []) ps
|
||||
case r_ of
|
||||
Just r' -> go (r' : rs) ps'
|
||||
Nothing -> error $ "unexpected event: " <> show r
|
||||
choose r p (Nothing, ps') = (maybe (Nothing, p : ps') ((,ps') . Just)) <$> p r
|
||||
choose _ p (Just r, ps') = pure (Just r, p : ps')
|
||||
|
||||
testWithStoreLog :: SpecWith (ASrvTransport, AStoreType)
|
||||
testWithStoreLog =
|
||||
it "should store simplex queues to log and restore them after server restart" $ \(at@(ATransport t), msType) -> do
|
||||
@@ -1159,7 +1366,7 @@ testMessageServiceNotifications =
|
||||
deliverMessage rh rId rKey sh sId sKey nh2 "connection 1" dec
|
||||
deliverMessage rh rId'' rKey'' sh sId'' sKey'' nh2 "connection 2" dec''
|
||||
-- -- another client makes service subscription
|
||||
Resp "12" serviceId5 (SOKS 2) <- signSendRecv nh1 (C.APrivateAuthKey C.SEd25519 servicePK) ("12", serviceId, NSUBS)
|
||||
Resp "12" serviceId5 (SOKS 2 _) <- signSendRecv nh1 (C.APrivateAuthKey C.SEd25519 servicePK) ("12", serviceId, NSUBS)
|
||||
serviceId5 `shouldBe` serviceId
|
||||
Resp "" serviceId6 (ENDS 2) <- tGet1 nh2
|
||||
serviceId6 `shouldBe` serviceId
|
||||
@@ -1193,7 +1400,7 @@ testServiceNotificationsTwoRestarts =
|
||||
threadDelay 250000
|
||||
withSmpServerStoreLogOn ps testPort $ runTest2 t $ \sh rh ->
|
||||
testNtfServiceClient t serviceKeys $ \nh -> do
|
||||
Resp "2.1" serviceId' (SOKS n) <- signSendRecv nh (C.APrivateAuthKey C.SEd25519 servicePK) ("2.1", serviceId, NSUBS)
|
||||
Resp "2.1" serviceId' (SOKS n _) <- signSendRecv nh (C.APrivateAuthKey C.SEd25519 servicePK) ("2.1", serviceId, NSUBS)
|
||||
n `shouldBe` 1
|
||||
Resp "2.2" _ (SOK Nothing) <- signSendRecv rh rKey ("2.2", rId, SUB)
|
||||
serviceId' `shouldBe` serviceId
|
||||
@@ -1201,7 +1408,7 @@ testServiceNotificationsTwoRestarts =
|
||||
threadDelay 250000
|
||||
withSmpServerStoreLogOn ps testPort $ runTest2 t $ \sh rh ->
|
||||
testNtfServiceClient t serviceKeys $ \nh -> do
|
||||
Resp "3.1" _ (SOKS n) <- signSendRecv nh (C.APrivateAuthKey C.SEd25519 servicePK) ("3.1", serviceId, NSUBS)
|
||||
Resp "3.1" _ (SOKS n _) <- signSendRecv nh (C.APrivateAuthKey C.SEd25519 servicePK) ("3.1", serviceId, NSUBS)
|
||||
n `shouldBe` 1
|
||||
Resp "3.2" _ (SOK Nothing) <- signSendRecv rh rKey ("3.2", rId, SUB)
|
||||
deliverMessage rh rId rKey sh sId sKey nh "hello 3" dec
|
||||
|
||||
Reference in New Issue
Block a user