Skip to content

Commit 07604a1

Browse files
authored
agent: async command to set connection short link (setConnShortLinkAsync) (#1680)
1 parent c4b687b commit 07604a1

4 files changed

Lines changed: 118 additions & 20 deletions

File tree

src/Simplex/Messaging/Agent.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module Simplex.Messaging.Agent
4949
deleteUser,
5050
connRequestPQSupport,
5151
createConnectionAsync,
52+
setConnShortLinkAsync,
5253
joinConnectionAsync,
5354
allowConnectionAsync,
5455
acceptContactAsync,
@@ -345,6 +346,11 @@ createConnectionAsync :: ConnectionModeI c => AgentClient -> UserId -> ACorrId -
345346
createConnectionAsync c userId aCorrId enableNtfs = withAgentEnv c .:. newConnAsync c userId aCorrId enableNtfs
346347
{-# INLINE createConnectionAsync #-}
347348

349+
-- | Create or update user's contact connection short link (LSET command) asynchronously, no synchronous response
350+
setConnShortLinkAsync :: ConnectionModeI c => AgentClient -> ACorrId -> ConnId -> SConnectionMode c -> UserConnLinkData c -> Maybe CRClientData -> AE ()
351+
setConnShortLinkAsync c = withAgentEnv c .::. setConnShortLinkAsync' c
352+
{-# INLINE setConnShortLinkAsync #-}
353+
348354
-- | Join SMP agent connection (JOIN command) asynchronously, synchronous response is new connection id
349355
joinConnectionAsync :: AgentClient -> UserId -> ACorrId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AE ConnId
350356
joinConnectionAsync c userId aCorrId enableNtfs = withAgentEnv c .:: joinConnAsync c userId aCorrId enableNtfs
@@ -886,6 +892,16 @@ checkClientNotices AgentClient {clientNotices, presetServers} (ProtoServerWithAu
886892
when (maybe True (ts <) expires_) $
887893
throwError NOTICE {server = safeDecodeUtf8 $ strEncode $ L.head host, preset = isNothing srvKey, expiresAt = roundedToUTCTime <$> expires_}
888894

895+
setConnShortLinkAsync' :: forall c. ConnectionModeI c => AgentClient -> ACorrId -> ConnId -> SConnectionMode c -> UserConnLinkData c -> Maybe CRClientData -> AM ()
896+
setConnShortLinkAsync' c corrId connId cMode userLinkData clientData =
897+
withConnLock c connId "setConnShortLinkAsync" $ do
898+
SomeConn _ conn <- withStore c (`getConn` connId)
899+
srv <- case (conn, cMode, userLinkData) of
900+
(ContactConnection _ RcvQueue {server}, SCMContact, UserContactLinkData {}) -> pure server
901+
(RcvConnection _ RcvQueue {server}, SCMInvitation, UserInvLinkData {}) -> pure server
902+
_ -> throwE $ CMD PROHIBITED "setConnShortLinkAsync: invalid connection or mode"
903+
enqueueCommand c corrId connId (Just srv) $ AClientCommand $ LSET (AUCLD cMode userLinkData) clientData
904+
889905
setConnShortLink' :: AgentClient -> NetworkRequestMode -> ConnId -> SConnectionMode c -> UserConnLinkData c -> Maybe CRClientData -> AM (ConnShortLink c)
890906
setConnShortLink' c nm connId cMode userLinkData clientData =
891907
withConnLock c connId "setConnShortLink" $ do
@@ -1657,6 +1673,10 @@ runCommandProcessing c@AgentClient {subQ} connId server_ Worker {doWork} = do
16571673
tryCommand . withNextSrv c userId storageSrvs triedHosts [] $ \srv -> do
16581674
(CCLink cReq _, service) <- newRcvConnSrv c NRMBackground userId connId enableNtfs cMode Nothing Nothing pqEnc subMode srv
16591675
notify $ INV (ACR cMode cReq) service
1676+
LSET auData@(AUCLD cMode userLinkData) clientData ->
1677+
withServer' . tryCommand $ do
1678+
link <- setConnShortLink' c NRMBackground connId cMode userLinkData clientData
1679+
notify $ LINK (ACSL cMode link) auData
16601680
JOIN enableNtfs (ACR _ cReq@(CRInvitationUri ConnReqUriData {crSmpQueues = q :| _} _)) pqEnc subMode connInfo -> noServer $ do
16611681
triedHosts <- newTVarIO S.empty
16621682
tryCommand . withNextSrv c userId storageSrvs triedHosts [qServer q] $ \srv -> do

src/Simplex/Messaging/Agent/Protocol.hs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ module Simplex.Messaging.Agent.Protocol
112112
ServiceScheme,
113113
FixedLinkData (..),
114114
ConnLinkData (..),
115+
AUserConnLinkData (..),
115116
UserConnLinkData (..),
116117
UserContactData (..),
117118
UserLinkData (..),
@@ -382,6 +383,7 @@ type SndQueueSecured = Bool
382383
-- | Parameterized type for SMP agent events
383384
data AEvent (e :: AEntity) where
384385
INV :: AConnectionRequestUri -> Maybe ClientServiceId -> AEvent AEConn
386+
LINK :: AConnShortLink -> AUserConnLinkData -> AEvent AEConn
385387
CONF :: ConfirmationId -> PQSupport -> [SMPServer] -> ConnInfo -> AEvent AEConn -- ConnInfo is from sender, [SMPServer] will be empty only in v1 handshake
386388
REQ :: InvitationId -> PQSupport -> NonEmpty SMPServer -> ConnInfo -> AEvent AEConn -- ConnInfo is from sender
387389
INFO :: PQSupport -> ConnInfo -> AEvent AEConn
@@ -435,6 +437,7 @@ deriving instance Show AEvtTag
435437

436438
data ACommand
437439
= NEW Bool AConnectionMode InitialKeys SubscriptionMode -- response INV
440+
| LSET AUserConnLinkData (Maybe CRClientData) -- response LINK
438441
| JOIN Bool AConnectionRequestUri PQSupport SubscriptionMode ConnInfo
439442
| LET ConfirmationId ConnInfo -- ConnInfo is from client
440443
| ACK AgentMsgId (Maybe MsgReceiptInfo)
@@ -444,6 +447,7 @@ data ACommand
444447

445448
data ACommandTag
446449
= NEW_
450+
| LSET_
447451
| JOIN_
448452
| LET_
449453
| ACK_
@@ -453,6 +457,7 @@ data ACommandTag
453457

454458
data AEventTag (e :: AEntity) where
455459
INV_ :: AEventTag AEConn
460+
LINK_ :: AEventTag AEConn
456461
CONF_ :: AEventTag AEConn
457462
REQ_ :: AEventTag AEConn
458463
INFO_ :: AEventTag AEConn
@@ -499,6 +504,7 @@ deriving instance Show (AEventTag e)
499504
aCommandTag :: ACommand -> ACommandTag
500505
aCommandTag = \case
501506
NEW {} -> NEW_
507+
LSET {} -> LSET_
502508
JOIN {} -> JOIN_
503509
LET {} -> LET_
504510
ACK {} -> ACK_
@@ -508,6 +514,7 @@ aCommandTag = \case
508514
aEventTag :: AEvent e -> AEventTag e
509515
aEventTag = \case
510516
INV {} -> INV_
517+
LINK {} -> LINK_
511518
CONF {} -> CONF_
512519
REQ {} -> REQ_
513520
INFO {} -> INFO_
@@ -1703,15 +1710,30 @@ data UserContactData = UserContactData
17031710
relays :: [ConnShortLink 'CMContact],
17041711
userData :: UserLinkData
17051712
}
1713+
deriving (Eq, Show)
17061714

17071715
newtype UserLinkData = UserLinkData ByteString
1716+
deriving (Eq, Show)
17081717

17091718
data AConnLinkData = forall m. ConnectionModeI m => ACLD (SConnectionMode m) (ConnLinkData m)
17101719

17111720
data UserConnLinkData c where
17121721
UserInvLinkData :: UserLinkData -> UserConnLinkData 'CMInvitation
17131722
UserContactLinkData :: UserContactData -> UserConnLinkData 'CMContact
17141723

1724+
deriving instance Eq (UserConnLinkData m)
1725+
1726+
deriving instance Show (UserConnLinkData m)
1727+
1728+
data AUserConnLinkData = forall m. ConnectionModeI m => AUCLD (SConnectionMode m) (UserConnLinkData m)
1729+
1730+
instance Eq AUserConnLinkData where
1731+
AUCLD m d == AUCLD m' d' = case testEquality m m' of
1732+
Just Refl -> d == d'
1733+
Nothing -> False
1734+
1735+
deriving instance Show AUserConnLinkData
1736+
17151737
linkUserData :: ConnLinkData c -> UserLinkData
17161738
linkUserData = \case
17171739
InvitationLinkData _ d -> d
@@ -1738,6 +1760,7 @@ data OwnerAuth = OwnerAuth
17381760
-- Owner validation should detect and reject loops.
17391761
authOwnerSig :: C.Signature 'C.Ed25519
17401762
}
1763+
deriving (Eq, Show)
17411764

17421765
instance Encoding OwnerAuth where
17431766
smpEncode OwnerAuth {ownerId, ownerKey, ownerSig, authOwnerId, authOwnerSig} =
@@ -1756,8 +1779,7 @@ instance ConnectionModeI c => Encoding (FixedLinkData c) where
17561779
instance ConnectionModeI c => Encoding (ConnLinkData c) where
17571780
smpEncode = \case
17581781
InvitationLinkData vr userData -> smpEncode (CMInvitation, vr, userData)
1759-
ContactLinkData vr UserContactData {direct, owners, relays, userData} ->
1760-
B.concat [smpEncode (CMContact, vr, direct), smpEncodeList owners, smpEncodeList relays, smpEncode userData]
1782+
ContactLinkData vr cd -> smpEncode (CMContact, vr, cd)
17611783
smpP = (\(ACLD _ d) -> checkConnMode d) <$?> smpP
17621784
{-# INLINE smpP #-}
17631785

@@ -1770,13 +1792,43 @@ instance Encoding AConnLinkData where
17701792
(vr, userData) <- smpP <* A.takeByteString -- ignoring tail for forward compatibility with the future link data encoding
17711793
pure $ ACLD SCMInvitation $ InvitationLinkData vr userData
17721794
CMContact -> do
1773-
(vr, direct) <- smpP
1774-
owners <- smpListP
1775-
relays <- smpListP
1776-
userData <- smpP <* A.takeByteString -- ignoring tail for forward compatibility with the future link data encoding
1777-
let cd = UserContactData {direct, owners, relays, userData}
1795+
(vr, cd) <- smpP
17781796
pure $ ACLD SCMContact $ ContactLinkData vr cd
17791797

1798+
instance ConnectionModeI c => Encoding (UserConnLinkData c) where
1799+
smpEncode = \case
1800+
UserInvLinkData userData -> smpEncode (CMInvitation, userData)
1801+
UserContactLinkData cd -> smpEncode (CMContact, cd)
1802+
smpP = (\(AUCLD _ d) -> checkConnMode d) <$?> smpP
1803+
{-# INLINE smpP #-}
1804+
1805+
instance Encoding AUserConnLinkData where
1806+
smpEncode (AUCLD _ d) = smpEncode d
1807+
{-# INLINE smpEncode #-}
1808+
smpP =
1809+
smpP >>= \case
1810+
CMInvitation -> do
1811+
userData <- smpP <* A.takeByteString -- ignoring tail for forward compatibility with the future link data encoding
1812+
pure $ AUCLD SCMInvitation $ UserInvLinkData userData
1813+
CMContact ->
1814+
AUCLD SCMContact . UserContactLinkData <$> smpP
1815+
1816+
instance StrEncoding AUserConnLinkData where
1817+
strEncode = smpEncode
1818+
{-# INLINE strEncode #-}
1819+
strP = smpP
1820+
{-# INLINE strP #-}
1821+
1822+
instance Encoding UserContactData where
1823+
smpEncode UserContactData {direct, owners, relays, userData} =
1824+
B.concat [smpEncode direct, smpEncodeList owners, smpEncodeList relays, smpEncode userData]
1825+
smpP = do
1826+
direct <- smpP
1827+
owners <- smpListP
1828+
relays <- smpListP
1829+
userData <- smpP <* A.takeByteString -- ignoring tail for forward compatibility with the future link data encoding
1830+
pure UserContactData {direct, owners, relays, userData}
1831+
17801832
instance Encoding UserLinkData where
17811833
smpEncode (UserLinkData s) = if B.length s <= 254 then smpEncode s else smpEncode ('\255', Large s)
17821834
{-# INLINE smpEncode #-}
@@ -1976,6 +2028,7 @@ instance StrEncoding ACommandTag where
19762028
strP =
19772029
A.takeTill (== ' ') >>= \case
19782030
"NEW" -> pure NEW_
2031+
"LSET" -> pure LSET_
19792032
"JOIN" -> pure JOIN_
19802033
"LET" -> pure LET_
19812034
"ACK" -> pure ACK_
@@ -1984,6 +2037,7 @@ instance StrEncoding ACommandTag where
19842037
_ -> fail "bad ACommandTag"
19852038
strEncode = \case
19862039
NEW_ -> "NEW"
2040+
LSET_ -> "LSET"
19872041
JOIN_ -> "JOIN"
19882042
LET_ -> "LET"
19892043
ACK_ -> "ACK"
@@ -1995,6 +2049,7 @@ commandP binaryP =
19952049
strP
19962050
>>= \case
19972051
NEW_ -> s (NEW <$> strP_ <*> strP_ <*> pqIKP <*> (strP <|> pure SMP.SMSubscribe))
2052+
LSET_ -> s (LSET <$> strP <*> optional (A.space *> strP))
19982053
JOIN_ -> s (JOIN <$> strP_ <*> strP_ <*> pqSupP <*> (strP_ <|> pure SMP.SMSubscribe) <*> binaryP)
19992054
LET_ -> s (LET <$> A.takeTill (== ' ') <* A.space <*> binaryP)
20002055
ACK_ -> s (ACK <$> A.decimal <*> optional (A.space *> binaryP))
@@ -2012,6 +2067,7 @@ commandP binaryP =
20122067
serializeCommand :: ACommand -> ByteString
20132068
serializeCommand = \case
20142069
NEW ntfs cMode pqIK subMode -> s (NEW_, ntfs, cMode, pqIK, subMode)
2070+
LSET uld cd_ -> s (LSET_, uld) <> maybe "" (B.cons ' ' . s) cd_
20152071
JOIN ntfs cReq pqSup subMode cInfo -> s (JOIN_, ntfs, cReq, pqSup, subMode, Str $ serializeBinary cInfo)
20162072
LET confId cInfo -> B.unwords [s LET_, confId, serializeBinary cInfo]
20172073
ACK mId rcptInfo_ -> s (ACK_, mId) <> maybe "" (B.cons ' ' . serializeBinary) rcptInfo_

tests/AgentTests/EqInstances.hs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module AgentTests.EqInstances where
66

77
import Data.Type.Equality
8-
import Simplex.Messaging.Agent.Protocol (ConnLinkData (..), OwnerAuth (..), UserContactData (..), UserLinkData (..))
8+
import Simplex.Messaging.Agent.Protocol (ConnLinkData (..))
99
import Simplex.Messaging.Agent.Store
1010
import Simplex.Messaging.Client (ProxiedRelay (..))
1111

@@ -32,18 +32,6 @@ deriving instance Show (ConnLinkData c)
3232

3333
deriving instance Eq (ConnLinkData c)
3434

35-
deriving instance Show UserContactData
36-
37-
deriving instance Eq UserContactData
38-
39-
deriving instance Show UserLinkData
40-
41-
deriving instance Eq UserLinkData
42-
43-
deriving instance Show OwnerAuth
44-
45-
deriving instance Eq OwnerAuth
46-
4735
deriving instance Show ProxiedRelay
4836

4937
deriving instance Eq ProxiedRelay

tests/AgentTests/FunctionalAPITests.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ functionalAPITests ps = do
452452
describe "Async agent commands" $ do
453453
describe "connect using async agent commands" $
454454
testBasicMatrix2 ps testAsyncCommands
455+
it "should add short link data using async agent command" $
456+
testSetConnShortLinkAsync ps
455457
it "should restore and complete async commands on restart" $
456458
testAsyncCommandsRestore ps
457459
describe "accept connection using async command" $
@@ -2628,6 +2630,38 @@ testAsyncCommands sqSecured alice bob baseId =
26282630
where
26292631
msgId = subtract baseId
26302632

2633+
testSetConnShortLinkAsync :: (ASrvTransport, AStoreType) -> IO ()
2634+
testSetConnShortLinkAsync ps = withAgentClients2 $ \alice bob ->
2635+
withSmpServerStoreLogOn ps testPort $ \_ -> runRight_ $ do
2636+
let userData = UserLinkData "test user data"
2637+
userCtData = UserContactData {direct = True, owners = [], relays = [], userData}
2638+
newLinkData = UserContactLinkData userCtData
2639+
(cId, (CCLink qInfo (Just shortLink), _)) <- A.createConnection alice NRMInteractive 1 True True SCMContact (Just newLinkData) Nothing IKPQOn SMSubscribe
2640+
-- verify initial link data
2641+
(_, ContactLinkData _ userCtData') <- getConnShortLink bob 1 shortLink
2642+
liftIO $ userCtData' `shouldBe` userCtData
2643+
-- update link data async
2644+
let updatedData = UserLinkData "updated user data"
2645+
updatedCtData = UserContactData {direct = False, owners = [], relays = [], userData = updatedData}
2646+
setConnShortLinkAsync alice "1" cId SCMContact (UserContactLinkData updatedCtData) Nothing
2647+
("1", cId', LINK (ACSL SCMContact shortLink') (AUCLD SCMContact (UserContactLinkData updatedCtData'))) <- get alice
2648+
liftIO $ cId' `shouldBe` cId
2649+
liftIO $ shortLink' `shouldBe` shortLink
2650+
liftIO $ updatedCtData' `shouldBe` updatedCtData
2651+
-- verify updated link data
2652+
(_, ContactLinkData _ updatedCtData'') <- getConnShortLink bob 1 shortLink'
2653+
liftIO $ updatedCtData'' `shouldBe` updatedCtData
2654+
-- complete connection via contact address
2655+
(aliceId, _) <- joinConnection bob 1 True qInfo "bob's connInfo" SMSubscribe
2656+
("", _, REQ invId _ "bob's connInfo") <- get alice
2657+
bobId <- A.prepareConnectionToAccept alice 1 True invId PQSupportOn
2658+
(_, Nothing) <- acceptContact alice 1 bobId True invId "alice's connInfo" PQSupportOn SMSubscribe
2659+
("", _, CONF confId _ "alice's connInfo") <- get bob
2660+
allowConnection bob aliceId confId "bob's connInfo"
2661+
get alice ##> ("", bobId, INFO "bob's connInfo")
2662+
get alice ##> ("", bobId, CON)
2663+
get bob ##> ("", aliceId, CON)
2664+
26312665
testAsyncCommandsRestore :: (ASrvTransport, AStoreType) -> IO ()
26322666
testAsyncCommandsRestore ps = do
26332667
alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB

0 commit comments

Comments
 (0)