@@ -151,7 +151,7 @@ import qualified Data.Aeson.TH as JQ
151151import Data.Bifunctor (bimap , first )
152152import Data.ByteString.Char8 (ByteString )
153153import qualified Data.ByteString.Char8 as B
154- import Data.Composition ( (.:) , (.:.) , (.::) , (.::.) )
154+ import Data.Composition
155155import Data.Either (isRight , partitionEithers , rights )
156156import Data.Foldable (foldl' , toList )
157157import Data.Functor (($>) )
@@ -189,10 +189,11 @@ import Simplex.Messaging.Agent.Store
189189import Simplex.Messaging.Agent.Store.AgentStore
190190import Simplex.Messaging.Agent.Store.Common (DBStore )
191191import qualified Simplex.Messaging.Agent.Store.DB as DB
192+ import Simplex.Messaging.Agent.Store.Entity
192193import Simplex.Messaging.Agent.Store.Interface (closeDBStore , execSQL , getCurrentMigrations )
193194import Simplex.Messaging.Agent.Store.Shared (UpMigration (.. ), upMigration )
194195import qualified Simplex.Messaging.Agent.TSessionSubs as SS
195- import Simplex.Messaging.Client (NetworkRequestMode (.. ), SMPClientError , ServerTransmission (.. ), ServerTransmissionBatch , nonBlockingWriteTBQueue , temporaryClientError , unexpectedResponse )
196+ import Simplex.Messaging.Client (NetworkRequestMode (.. ), SMPClientError , ServerTransmission (.. ), ServerTransmissionBatch , nonBlockingWriteTBQueue , smpErrorClientNotice , temporaryClientError , unexpectedResponse )
196197import qualified Simplex.Messaging.Crypto as C
197198import Simplex.Messaging.Crypto.File (CryptoFile , CryptoFileArgs )
198199import Simplex.Messaging.Crypto.Ratchet (PQEncryption , PQSupport (.. ), pattern PQEncOff , pattern PQEncOn , pattern PQSupportOff , pattern PQSupportOn )
@@ -227,7 +228,7 @@ import Simplex.Messaging.Protocol
227228 )
228229import qualified Simplex.Messaging.Protocol as SMP
229230import Simplex.Messaging.ServiceScheme (ServiceScheme (.. ))
230- import Simplex.Messaging.Agent.Store.Entity
231+ import Simplex.Messaging.SystemTime
231232import qualified Simplex.Messaging.TMap as TM
232233import Simplex.Messaging.Transport (SMPVersion )
233234import Simplex.Messaging.Util
@@ -251,13 +252,14 @@ getSMPAgentClient = getSMPAgentClient_ 1
251252{-# INLINE getSMPAgentClient #-}
252253
253254getSMPAgentClient_ :: Int -> AgentConfig -> InitialAgentServers -> DBStore -> Bool -> IO AgentClient
254- getSMPAgentClient_ clientId cfg initServers@ InitialAgentServers {smp, xftp} store backgroundMode =
255+ getSMPAgentClient_ clientId cfg initServers@ InitialAgentServers {smp, xftp, presetServers } store backgroundMode =
255256 newSMPAgentEnv cfg store >>= runReaderT runAgent
256257 where
257258 runAgent = do
258259 liftIO $ checkServers " SMP" smp >> checkServers " XFTP" xftp
259260 currentTs <- liftIO getCurrentTime
260- c@ AgentClient {acThread} <- liftIO . newAgentClient clientId initServers currentTs =<< ask
261+ notices <- liftIO $ withTransaction store (`getClientNotices` presetServers) `catchAll_` pure []
262+ c@ AgentClient {acThread} <- liftIO . newAgentClient clientId initServers currentTs notices =<< ask
261263 t <- runAgentThreads c `forkFinally` const (liftIO $ disconnectAgentClient c)
262264 atomically . writeTVar acThread . Just =<< mkWeakThreadId t
263265 pure c
@@ -379,8 +381,8 @@ deleteConnectionsAsync c waitDelivery = withAgentEnv c . deleteConnectionsAsync'
379381{-# INLINE deleteConnectionsAsync #-}
380382
381383-- | Create SMP agent connection (NEW command)
382- createConnection :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> SConnectionMode c -> Maybe UserLinkData -> Maybe CRClientData -> CR. InitialKeys -> SubscriptionMode -> AE (ConnId , (CreatedConnLink c , Maybe ClientServiceId ))
383- createConnection c nm userId enableNtfs = withAgentEnv c .::. newConn c nm userId enableNtfs
384+ createConnection :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> Bool -> SConnectionMode c -> Maybe UserLinkData -> Maybe CRClientData -> CR. InitialKeys -> SubscriptionMode -> AE (ConnId , (CreatedConnLink c , Maybe ClientServiceId ))
385+ createConnection c nm userId enableNtfs checkNotices = withAgentEnv c .::. newConn c nm userId enableNtfs checkNotices
384386{-# INLINE createConnection #-}
385387
386388-- | Create or update user's contact connection short link
@@ -863,13 +865,27 @@ switchConnectionAsync' c corrId connId =
863865 connectionStats c $ DuplexConnection cData rqs' sqs
864866 _ -> throwE $ CMD PROHIBITED " switchConnectionAsync: not duplex"
865867
866- newConn :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> SConnectionMode c -> Maybe UserLinkData -> Maybe CRClientData -> CR. InitialKeys -> SubscriptionMode -> AM (ConnId , (CreatedConnLink c , Maybe ClientServiceId ))
867- newConn c nm userId enableNtfs cMode userData_ clientData pqInitKeys subMode = do
868+ newConn :: ConnectionModeI c => AgentClient -> NetworkRequestMode -> UserId -> Bool -> Bool -> SConnectionMode c -> Maybe UserLinkData -> Maybe CRClientData -> CR. InitialKeys -> SubscriptionMode -> AM (ConnId , (CreatedConnLink c , Maybe ClientServiceId ))
869+ newConn c nm userId enableNtfs checkNotices cMode userData_ clientData pqInitKeys subMode = do
868870 srv <- getSMPServer c userId
871+ when (checkNotices && connMode cMode == CMContact ) $ checkClientNotices c srv
869872 connId <- newConnNoQueues c userId enableNtfs cMode (CR. connPQEncryption pqInitKeys)
870873 (connId,) <$> newRcvConnSrv c nm userId connId enableNtfs cMode userData_ clientData pqInitKeys subMode srv
871874 `catchE` \ e -> withStore' c (`deleteConnRecord` connId) >> throwE e
872875
876+ checkClientNotices :: AgentClient -> SMPServerWithAuth -> AM ()
877+ checkClientNotices AgentClient {clientNotices, presetServers} (ProtoServerWithAuth srv@ (ProtocolServer {host}) _) = do
878+ notices <- readTVarIO clientNotices
879+ unless (M. null notices) $ checkNotices notices =<< liftIO getSystemSeconds
880+ where
881+ srvKey
882+ | isPresetServer srv presetServers = Nothing -- Nothing is used as key for preset servers
883+ | otherwise = Just srv
884+ checkNotices notices ts =
885+ forM_ (M. lookup srvKey notices) $ \ expires_ ->
886+ when (maybe True (ts < ) expires_) $
887+ throwError NOTICE {server = safeDecodeUtf8 $ strEncode $ L. head host, preset = isNothing srvKey, expiresAt = roundedToUTCTime <$> expires_}
888+
873889setConnShortLink' :: AgentClient -> NetworkRequestMode -> ConnId -> SConnectionMode c -> UserLinkData -> Maybe CRClientData -> AM (ConnShortLink c )
874890setConnShortLink' c nm connId cMode userData clientData =
875891 withConnLock c connId " setConnShortLink" $ do
@@ -2794,18 +2810,20 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
27942810 STEvent msgOrErr ->
27952811 withRcvConn entId $ \ rq@ RcvQueue {connId} conn -> case msgOrErr of
27962812 Right msg -> runProcessSMP rq conn (toConnData conn) msg
2797- Left e -> lift $ notifyErr connId e
2813+ Left e -> lift $ do
2814+ processClientNotice rq e
2815+ notifyErr connId e
27982816 STResponse (Cmd SRecipient cmd) respOrErr ->
27992817 withRcvConn entId $ \ rq conn -> case cmd of
28002818 SMP. SUB -> case respOrErr of
2801- Right SMP. OK -> processSubOk rq upConnIds
2819+ Right SMP. OK -> liftIO $ processSubOk rq upConnIds
28022820 -- TODO [certs rcv] associate queue with the service
2803- Right (SMP. SOK serviceId_) -> processSubOk rq upConnIds
2821+ Right (SMP. SOK serviceId_) -> liftIO $ processSubOk rq upConnIds
28042822 Right msg@ SMP. MSG {} -> do
2805- processSubOk rq upConnIds -- the connection is UP even when processing this particular message fails
2823+ liftIO $ processSubOk rq upConnIds -- the connection is UP even when processing this particular message fails
28062824 runProcessSMP rq conn (toConnData conn) msg
2807- Right r -> processSubErr rq $ unexpectedResponse r
2808- Left e -> unless (temporaryClientError e) $ processSubErr rq e -- timeout/network was already reported
2825+ Right r -> lift $ processSubErr rq $ unexpectedResponse r
2826+ Left e -> lift $ unless (temporaryClientError e) $ processSubErr rq e -- timeout/network was already reported
28092827 SMP. ACK _ -> case respOrErr of
28102828 Right msg@ SMP. MSG {} -> runProcessSMP rq conn (toConnData conn) msg
28112829 _ -> pure () -- TODO process OK response to ACK
@@ -2827,21 +2845,28 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
28272845 tryAllErrors' (a rq conn) >>= \ case
28282846 Left e -> notify' connId (ERR e)
28292847 Right () -> pure ()
2830- processSubOk :: RcvQueue -> TVar [ConnId ] -> AM ()
2848+ processSubOk :: RcvQueue -> TVar [ConnId ] -> IO ()
28312849 processSubOk rq@ RcvQueue {connId} upConnIds =
28322850 atomically . whenM (isPendingSub rq) $ do
28332851 SS. addActiveSub tSess sessId (rcvQueueSub rq) $ currentSubs c
28342852 modifyTVar' upConnIds (connId : )
2835- processSubErr :: RcvQueue -> SMPClientError -> AM ()
2853+ processSubErr :: RcvQueue -> SMPClientError -> AM' ()
28362854 processSubErr rq@ RcvQueue {connId} e = do
28372855 atomically . whenM (isPendingSub rq) $
28382856 failSubscription c tSess rq e >> incSMPServerStat c userId srv connSubErrs
2839- lift $ notifyErr connId e
2857+ processClientNotice rq e
2858+ notifyErr connId e
28402859 isPendingSub :: RcvQueue -> STM Bool
28412860 isPendingSub rq = do
28422861 pending <- (&&) <$> SS. hasPendingSub tSess (queueId rq) (currentSubs c) <*> activeClientSession c tSess sessId
28432862 unless pending $ incSMPServerStat c userId srv connSubIgnored
28442863 pure pending
2864+ processClientNotice rq e =
2865+ forM_ (smpErrorClientNotice e) $ \ notice_ ->
2866+ E. bracket_
2867+ (atomically $ takeTMVar $ clientNoticesLock c)
2868+ (atomically $ putTMVar (clientNoticesLock c) () )
2869+ (processClientNotices c tSess [(rcvQueueSub rq, notice_)])
28452870 notify' :: forall e m . (AEntityI e , MonadIO m ) => ConnId -> AEvent e -> m ()
28462871 notify' connId msg = atomically $ writeTBQueue subQ (" " , connId, AEvt (sAEntity @ e ) msg)
28472872 notifyErr :: ConnId -> SMPClientError -> AM' ()
0 commit comments