on master help sign in
Simplify code and improve type safety: combine auth flow, make response types private, normalize TextEnvelope naming, remove no-op message
6bfbd45iko
client/ApiClient.fs+10−14
@@ -22,10 +22,10 @@ module ApiClient =
22 type PollResponse = { Cursor: uint64; Events: PollEvent array }
22 type PollResponse = { Cursor: uint64; Events: PollEvent array }
23
23
24 [<CLIMutable>]
24 [<CLIMutable>]
25− type ChallengeResponse = { challenge: string }
25+ type private ChallengeResponse = { challenge: string }
26
26
27 [<CLIMutable>]
27 [<CLIMutable>]
28− type VerifyResponse = { token: string }
28+ type private VerifyResponse = { token: string }
29
29
30 /// Awaits a Task without converting TaskCanceledException to F# async
30 /// Awaits a Task without converting TaskCanceledException to F# async
31 /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
31 /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
@@ -83,19 +83,15 @@ module ApiClient =
83 return JsonSerializer.Deserialize<'T>(text, jsonOpts)
83 return JsonSerializer.Deserialize<'T>(text, jsonOpts)
84 }
84 }
85
85
86− let requestChallenge (serverUrl: string) (pubkeyHex: string) =
86+ let authenticate (serverUrl: string) (identity: Crypto.Identity) =
87 async {
87 async {
88− let body = JsonSerializer.Serialize({| pubkey = pubkeyHex |})
88+ let body = JsonSerializer.Serialize({| pubkey = identity.PubKeyHex |})
89− let! response = postJson<ChallengeResponse> client $"{serverUrl}/auth/challenge" body None
89+ let! resp = postJson<ChallengeResponse> client $"{serverUrl}/auth/challenge" body None
90− return response.challenge
90+ let sigHex = Crypto.signChallenge identity.PrivKey resp.challenge
91− }
92−
93− let verify (serverUrl: string) (pubkeyHex: string) (challenge: string) (signatureHex: string) =
94− async {
95− let body = JsonSerializer.Serialize({| pubkey = pubkeyHex; challenge = challenge; signature = signatureHex |})
91+ let body = JsonSerializer.Serialize({| pubkey = identity.PubKeyHex; challenge = resp.challenge; signature = sigHex |})
96− let! response = postJson<VerifyResponse> client $"{serverUrl}/auth/verify" body None
92+ let! resp = postJson<VerifyResponse> client $"{serverUrl}/auth/verify" body None
97− if String.IsNullOrEmpty(response.token) then failwith "Authentication rejected by server"
93+ if String.IsNullOrEmpty(resp.token) then failwith "Authentication rejected by server"
98− return response.token
94+ return resp.token
99 }
95 }
100
96
101 let sendMessage (serverUrl: string) (token: string) (toHex: string) (blobHex: string) (timestamp: int64) =
97 let sendMessage (serverUrl: string) (token: string) (toHex: string) (blobHex: string) (timestamp: int64) =
client/App.fs+17−19
@@ -25,9 +25,9 @@ module App =
25 [<CLIMutable>]
25 [<CLIMutable>]
26 type TextEnvelope =
26 type TextEnvelope =
27 { [<JsonPropertyName("type")>] Type: string
27 { [<JsonPropertyName("type")>] Type: string
28− id: string
28+ [<JsonPropertyName("id")>] Id: string
29− timestamp: int64
29+ [<JsonPropertyName("timestamp")>] Timestamp: int64
30− body: string }
30+ [<JsonPropertyName("body")>] Body: string }
31
31
32 type Page =
32 type Page =
33 | Setup
33 | Setup
@@ -82,7 +82,6 @@ module App =
82 | PollResult of messages: (string * ChatMessage) list * status: string * cursor: uint64
82 | PollResult of messages: (string * ChatMessage) list * status: string * cursor: uint64
83 | CopyPubKey
83 | CopyPubKey
84 | DismissError
84 | DismissError
85− | NoIdentityFound
86 | StateLoaded of Identity * Store.DataDto option
85 | StateLoaded of Identity * Store.DataDto option
87
86
88 // ── CmdMsg ──
87 // ── CmdMsg ──
@@ -255,7 +254,6 @@ module App =
255
254
256 | DismissError -> { model with Error = None }, []
255 | DismissError -> { model with Error = None }, []
257
256
258− | NoIdentityFound -> model, []
259 | StateLoaded(identity, dataOpt) ->
257 | StateLoaded(identity, dataOpt) ->
260 let model' = { model with Identity = Some identity; Conn = Connecting identity }
258 let model' = { model with Identity = Some identity; Conn = Connecting identity }
261
259
@@ -306,8 +304,8 @@ module App =
306
304
307 if envelope.Type = "text" then
305 if envelope.Type = "text" then
308 Ok(senderHex,
306 Ok(senderHex,
309− { Id = envelope.id
307+ { Id = envelope.Id
310− Body = envelope.body
308+ Body = envelope.Body
311 Timestamp = DateTimeOffset.FromUnixTimeSeconds(payload.Timestamp)
309 Timestamp = DateTimeOffset.FromUnixTimeSeconds(payload.Timestamp)
312 IsOutgoing = false })
310 IsOutgoing = false })
313 else
311 else
@@ -324,9 +322,7 @@ module App =
324 | CmdConnect(url, identity) ->
322 | CmdConnect(url, identity) ->
325 asyncCmd (async {
323 asyncCmd (async {
326 try
324 try
327− let! challenge = ApiClient.requestChallenge url identity.PubKeyHex
328− let sigHex = Crypto.signChallenge identity.PrivKey challenge
329− let! token = ApiClient.verify url identity.PubKeyHex challenge sigHex
325+ let! token = ApiClient.authenticate url identity
330 return AuthOk token
326 return AuthOk token
331 with ex ->
327 with ex ->
332 return AuthErr ex.Message
328 return AuthErr ex.Message
@@ -339,7 +335,7 @@ module App =
339 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
335 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
340
336
341 let payload =
337 let payload =
342− JsonSerializer.Serialize({ Type = "text"; id = messageId; timestamp = ts; body = text }: TextEnvelope)
338+ JsonSerializer.Serialize({ Type = "text"; Id = messageId; Timestamp = ts; Body = text }: TextEnvelope)
343
339
344 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
340 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
345 let blobHex = Crypto.toHex blob
341 let blobHex = Crypto.toHex blob
@@ -386,14 +382,16 @@ module App =
386 |> ignore)
382 |> ignore)
387
383
388 | CmdLoadState ->
384 | CmdLoadState ->
385+ Cmd.ofEffect (fun dispatch ->
389− asyncCmd (async {
386+ async {
390− let! identity = Store.loadIdentity ()
387+ let! identity = Store.loadIdentity ()
391− match identity with
388+ match identity with
392− | None -> return NoIdentityFound
393− | Some id ->
389+ | Some id ->
394− let data = Store.loadData ()
390+ let data = Store.loadData ()
395− return StateLoaded(id, data)
391+ dispatch (StateLoaded(id, data))
392+ | None -> ()
396− })
393+ }
394+ |> Async.Start)
397
395
398 | CmdSaveIdentity identity ->
396 | CmdSaveIdentity identity ->
399 Cmd.ofEffect (fun _ -> Store.saveIdentity identity |> ignore)
397 Cmd.ofEffect (fun _ -> Store.saveIdentity identity |> ignore)
client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.pdb

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/_CodeSignature/CodeResources+2−2
@@ -240,14 +240,14 @@
240 <dict>
240 <dict>
241 <key>hash2</key>
241 <key>hash2</key>
242 <data>
242 <data>
243− kz4trELqvTzwqCWvJX0TwklUlGDMQ7PmstX/wX+L1ys=
243+ E9mXMT9TM9+PN5lZdr8hd7Pjdh/b/nQ62LyZ9LZgVuw=
244 </data>
244 </data>
245 </dict>
245 </dict>
246 <key>MonoBundle/Skrepka.pdb</key>
246 <key>MonoBundle/Skrepka.pdb</key>
247 <dict>
247 <dict>
248 <key>hash2</key>
248 <key>hash2</key>
249 <data>
249 <data>
250− R15BgIEVnOLSGbnwifZrUZ3UxFh8o3aOr8vzoOqfToE=
250+ zrk8Hm3qBzSS6q+50oo9lwNm5bRrdg7ggTxpygT5xx4=
251 </data>
251 </data>
252 </dict>
252 </dict>
253 <key>MonoBundle/Sodium.Core.dll</key>
253 <key>MonoBundle/Sodium.Core.dll</key>
client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfo.fs+1−1
@@ -10,7 +10,7 @@ open System.Reflection
10[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
10[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
11[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
11[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
12[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
12[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+61c0ab01502e0f3b882828035225b639018f6ac4")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+13e9b38829a3f3c286fdce62545e8c58101d5be8")>]
14[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
14[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
15[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
15[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
16[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
16[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−64ef513869c07b61515326591a2ef70b3274d7205ba59736fabaf7a6219440eb
1+b1928d787ef021db1e1d4b4e5fbe08f25e928393c52d9e3fd86e7dada5b32d58
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.sourcelink.json+1−1
@@ -1 +1 @@
1−{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/61c0ab01502e0f3b882828035225b639018f6ac4/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/13e9b38829a3f3c286fdce62545e8c58101d5be8/*"}}
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/ref/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/refint/Skrepka.dll

Binary file not shown.

server.knot+125−118
@@ -815,34 +815,35 @@ serve = \req -> case req of
815 yield SendUnauthorized {pr: pr}
815 yield SendUnauthorized {pr: pr}
816 Authorized {pubkey: fromKey} -> do
816 Authorized {pubkey: fromKey} -> do
817 messages <- *messages
817 messages <- *messages
818− presence <- *presence
819− seqCounter <- *seqCounter
820− let seqTs = nextSeqTs t seqCounter
821− let shouldInsert = countMessagesFor messages to t < maxMessagesPerRecipient
818+ if countMessagesFor messages to t >= maxMessagesPerRecipient
822− set *seqCounter = if shouldInsert then seqTs else seqCounter
819+ then yield SendRejected {pr: pr}
823− set *messages = if shouldInsert
820+ else do
821+ presence <- *presence
822+ seqCounter <- *seqCounter
823+ let seqTs = nextSeqTs t seqCounter
824+ set *seqCounter = seqTs
824− then union messages
825+ set *messages = union messages
825− [{messageId: msgId, toKey: to, fromKey: fromKey,
826+ [{messageId: msgId, toKey: to, fromKey: fromKey,
826− encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
827+ encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
827− else messages
828− let toPresence = do p <- presence; where p.pubkey == to; where p.expiresAt > t; yield p
828+ let toPresence = do p <- presence; where p.pubkey == to; where p.expiresAt > t; yield p
829− let localOnline = any toPresence (\p -> p.server == serverHostN)
829+ let localOnline = any toPresence (\p -> p.server == serverHostN)
830− let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
830+ let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
831− forwards <- *forwards
831+ forwards <- *forwards
832− let forwardServers = takeN remoteServers (maxForwardsEntries - count forwards)
832+ let forwardServers = takeN remoteServers (maxForwardsEntries - count forwards)
833− let status = if not shouldInsert then "rejected"
834− else if localOnline then "delivered"
833+ let status = if localOnline then "delivered"
835− else if federationEnabled && count forwardServers > 0 then "federated"
834+ else if federationEnabled && count forwardServers > 0 then "federated"
836− else "queued"
835+ else "queued"
837− set *forwards = if shouldInsert && federationEnabled && count forwardServers > 0
836+ set *forwards = if federationEnabled && count forwardServers > 0
838− then union forwards (do s <- forwardServers; yield {messageId: msgId, toServer: s, createdAt: t, toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob, timestamp: clampedTs})
837+ then union forwards (do s <- forwardServers; yield {messageId: msgId, toServer: s, createdAt: t, toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob, timestamp: clampedTs})
839− else forwards
838+ else forwards
840− yield SendDone {status: status, remoteServers: remoteServers, fromKey: fromKey, pr: pr}
839+ yield SendDone {status: status, remoteServers: remoteServers, fromKey: fromKey, pr: pr}
841 fork (case result of
840 fork (case result of
842 SendUnauthorized {pr} ->
841 SendUnauthorized {pr} ->
843 broadcastOfflineGossip t pr.peerServers pr.expiredLocal
842 broadcastOfflineGossip t pr.peerServers pr.expiredLocal
843+ SendRejected {pr} ->
844+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal
844 SendDone {status, remoteServers, fromKey, pr} -> do
845 SendDone {status, remoteServers, fromKey, pr} -> do
845− when (count remoteServers > 0 && not (status == "rejected") && federationEnabled)
846+ when (count remoteServers > 0 && federationEnabled)
846 (sendToEach remoteServers (\server -> do
847 (sendToEach remoteServers (\server -> do
847 fwdResult <- fetch (federationScheme ++ server) (ForwardMessage {
848 fwdResult <- fetch (federationScheme ++ server) (ForwardMessage {
848 toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob,
849 toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob,
@@ -856,7 +857,8 @@ serve = \req -> case req of
856 broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
857 broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
857 yield (case result of
858 yield (case result of
858 SendUnauthorized _ -> respond {status: "unauthorized", messageId: ""}
859 SendUnauthorized _ -> respond {status: "unauthorized", messageId: ""}
860+ SendRejected _ -> respond {status: "rejected", messageId: ""}
859− SendDone {status, remoteServers, fromKey, pr} -> respond {status: status, messageId: if status == "rejected" then "" else msgId})
861+ SendDone {status} -> respond {status: status, messageId: msgId})
860
862
861 AckMessages {messageIds, authorization, respond} -> do
863 AckMessages {messageIds, authorization, respond} -> do
862 t <- now
864 t <- now
@@ -956,82 +958,86 @@ serve = \req -> case req of
956 let authorized = verifyFederationAuth authorization
958 let authorized = verifyFederationAuth authorization
957 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
959 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
958 let fromServer = normalizeServerName fromServer
960 let fromServer = normalizeServerName fromServer
959− let validRequest = authorized && cappedHops >= 1 && not (fromServer == serverHostN) && count events <= maxGossipEvents
961+ if not (authorized && cappedHops >= 1 && not (fromServer == serverHostN) && count events <= maxGossipEvents)
962+ then do
963+ pruneResult <- atomic do maybePrune t
964+ forkBroadcastOffline t pruneResult
965+ yield (respond {ok: False {}})
966+ else do
960− let allValidEntries = if validRequest then validGossipEvents events t else []
967+ let allValidEntries = validGossipEvents events t
961− let onlineEntries = do e <- allValidEntries; where e.eventType == "online"; yield e
968+ let onlineEntries = do e <- allValidEntries; where e.eventType == "online"; yield e
962− let offlineEntries = do e <- allValidEntries; where e.eventType == "offline"; yield e
969+ let offlineEntries = do e <- allValidEntries; where e.eventType == "offline"; yield e
963− let filteredOnline = do
970+ let filteredOnline = do
964− e <- onlineEntries
971+ e <- onlineEntries
965− where none offlineEntries (\o -> o.server == fromServer && samePubkeyServer o e && o.timestamp > e.timestamp)
972+ where none offlineEntries (\o -> o.server == fromServer && samePubkeyServer o e && o.timestamp > e.timestamp)
966− yield e
973+ yield e
967− let filteredOffline = do
974+ let filteredOffline = do
968− e <- offlineEntries
975+ e <- offlineEntries
969− where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
976+ where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
970− yield e
977+ yield e
971− let trustedOffline = do
978+ let trustedOffline = do
972− e <- offlineEntries
979+ e <- offlineEntries
973− where e.server == fromServer
980+ where e.server == fromServer
974− yield e
981+ yield e
975− let deduped = mergeByPubkeyServer [] filteredOnline
982+ let deduped = mergeByPubkeyServer [] filteredOnline
976− gossipResult <- atomic do
983+ gossipResult <- atomic do
977− pr <- maybePrune t
984+ pr <- maybePrune t
978− presence <- *presence
985+ presence <- *presence
979− let trustedDeduped = do
986+ let trustedDeduped = do
980− e <- deduped
987+ e <- deduped
981− where e.server == fromServer || any presence (\p -> p.server == e.server && p.expiresAt > t)
988+ where e.server == fromServer || any presence (\p -> p.server == e.server && p.expiresAt > t)
982− yield e
989+ yield e
983− let appliedOnline = do
990+ let appliedOnline = do
984− e <- trustedDeduped
991+ e <- trustedDeduped
985− where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt)
992+ where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt)
986− yield {pubkey: e.pubkey, server: e.server}
993+ yield {pubkey: e.pubkey, server: e.server}
987− let merged = mergeByPubkeyServer presence trustedDeduped
994+ let merged = mergeByPubkeyServer presence trustedDeduped
988− let offlineOnlyKeys = do
995+ let offlineOnlyKeys = do
989− e <- allValidEntries
996+ e <- allValidEntries
990− where none trustedDeduped (\d -> samePubkeyServer d e)
997+ where none trustedDeduped (\d -> samePubkeyServer d e)
991− yield e
998+ yield e
992− let appliedOffline = do
999+ let appliedOffline = do
993− k <- offlineOnlyKeys
1000+ k <- offlineOnlyKeys
994− where any presence (\p -> samePubkeyServer p k && any trustedOffline (\o -> samePubkeyServer o k && (if o.timestamp > t then t else o.timestamp) > p.timestamp))
1001+ where any presence (\p -> samePubkeyServer p k && any trustedOffline (\o -> samePubkeyServer o k && (if o.timestamp > t then t else o.timestamp) > p.timestamp))
995− yield {pubkey: k.pubkey, server: k.server}
1002+ yield {pubkey: k.pubkey, server: k.server}
996− let afterOffline = removeOffline merged offlineOnlyKeys trustedOffline t
1003+ let afterOffline = removeOffline merged offlineOnlyKeys trustedOffline t
997− let withinCapacity = count afterOffline <= maxPresenceEntries
1004+ let withinCapacity = count afterOffline <= maxPresenceEntries
998− let capResult = if withinCapacity
1005+ let capResult = if withinCapacity
999− then {finalPresence: afterOffline, appliedOnline: appliedOnline}
1006+ then {finalPresence: afterOffline, appliedOnline: appliedOnline}
1000− else
1007+ else
1001− let updatesOnly = do e <- trustedDeduped; where any presence (\p -> samePubkeyServer p e); yield e in
1008+ let updatesOnly = do e <- trustedDeduped; where any presence (\p -> samePubkeyServer p e); yield e in
1002− let updatesOnlyMerged = mergeByPubkeyServer presence updatesOnly in
1009+ let updatesOnlyMerged = mergeByPubkeyServer presence updatesOnly in
1003− let updatesOnlyAfterOffline = removeOffline updatesOnlyMerged offlineOnlyKeys trustedOffline t in
1010+ let updatesOnlyAfterOffline = removeOffline updatesOnlyMerged offlineOnlyKeys trustedOffline t in
1004− let reclaimSlots = maxPresenceEntries - count updatesOnlyAfterOffline in
1011+ let reclaimSlots = maxPresenceEntries - count updatesOnlyAfterOffline in
1005− let newEntries = do e <- trustedDeduped; where none presence (\p -> samePubkeyServer p e); where none updatesOnlyAfterOffline (\p -> samePubkeyServer p e); yield e in
1012+ let newEntries = do e <- trustedDeduped; where none presence (\p -> samePubkeyServer p e); where none updatesOnlyAfterOffline (\p -> samePubkeyServer p e); yield e in
1006− let reclaimedEntries = takeN newEntries reclaimSlots in
1013+ let reclaimedEntries = takeN newEntries reclaimSlots in
1007− {finalPresence: union updatesOnlyAfterOffline reclaimedEntries,
1014+ {finalPresence: union updatesOnlyAfterOffline reclaimedEntries,
1008− appliedOnline: union
1015+ appliedOnline: union
1009− (do e <- updatesOnly; where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt); yield {pubkey: e.pubkey, server: e.server})
1016+ (do e <- updatesOnly; where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt); yield {pubkey: e.pubkey, server: e.server})
1010− (do e <- reclaimedEntries; yield {pubkey: e.pubkey, server: e.server})}
1017+ (do e <- reclaimedEntries; yield {pubkey: e.pubkey, server: e.server})}
1011− let finalPresence = if validRequest then capResult.finalPresence else presence
1012− set *presence = finalPresence
1018+ set *presence = capResult.finalPresence
1013− let gossipPeerServers = collectPeerServers finalPresence (\p -> p.server)
1019+ let gossipPeerServers = collectPeerServers capResult.finalPresence (\p -> p.server)
1014− yield {appliedKeys: union capResult.appliedOnline appliedOffline, pr: pr, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
1020+ yield {appliedKeys: union capResult.appliedOnline appliedOffline, pr: pr, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
1015− let effectiveEvents = do
1021+ let effectiveEvents = do
1016− e <- union filteredOnline filteredOffline
1022+ e <- union filteredOnline filteredOffline
1017− where any gossipResult.appliedKeys (\a -> samePubkeyServer a e)
1023+ where any gossipResult.appliedKeys (\a -> samePubkeyServer a e)
1018− yield {eventType: e.eventType, pubkey: e.pubkey, server: e.server, timestamp: e.timestamp, ttl: e.ttl}
1024+ yield {eventType: e.eventType, pubkey: e.pubkey, server: e.server, timestamp: e.timestamp, ttl: e.ttl}
1019− when (not gossipResult.withinCapacity)
1025+ when (not gossipResult.withinCapacity)
1020− (println ("gossip: presence at capacity (" ++ show maxPresenceEntries ++ "), limiting new entries from " ++ fromServer))
1026+ (println ("gossip: presence at capacity (" ++ show maxPresenceEntries ++ "), limiting new entries from " ++ fromServer))
1021− fork (do
1027+ fork (do
1022− when (validRequest && cappedHops > 1 && federationEnabled && count effectiveEvents > 0) (do
1028+ when (cappedHops > 1 && federationEnabled && count effectiveEvents > 0) (do
1023− let targets = do
1029+ let targets = do
1024− s <- gossipResult.gossipPeerServers
1030+ s <- gossipResult.gossipPeerServers
1025− where not (s == fromServer)
1031+ where not (s == fromServer)
1026− yield s
1032+ yield s
1027− sendToEach targets (\server -> do
1033+ sendToEach targets (\server -> do
1028− _ <- fetch (federationScheme ++ server) (RecvGossip {
1034+ _ <- fetch (federationScheme ++ server) (RecvGossip {
1029− authorization: "Bearer " ++ federationSecret,
1035+ authorization: "Bearer " ++ federationSecret,
1030− events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHostN})
1036+ events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHostN})
1031− yield {}))
1037+ yield {}))
1032− let pr = gossipResult.pr
1038+ let pr = gossipResult.pr
1033− broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
1039+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
1034− yield (respond {ok: validRequest})
1040+ yield (respond {ok: True {}})
1035
1041
1036 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
1042 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
1037 let toKey = normalizeHex toKey
1043 let toKey = normalizeHex toKey
@@ -1045,23 +1051,24 @@ serve = \req -> case req of
1045 let clampedTs = clampTimestamp timestamp t
1051 let clampedTs = clampTimestamp timestamp t
1046 result <- atomic do
1052 result <- atomic do
1047 pr <- maybePrune t
1053 pr <- maybePrune t
1048− messages <- *messages
1049− seqCounter <- *seqCounter
1050− let seqTs = nextSeqTs t seqCounter
1051− ackedIds <- *ackedIds
1052− let valid = authorized && validOrigin
1054+ if not (authorized && validOrigin)
1053− let alreadyExists = valid
1055+ then yield {pr: pr, ok: False {}}
1056+ else do
1057+ messages <- *messages
1058+ ackedIds <- *ackedIds
1054− && (any messages (\m -> m.messageId == messageId && m.toKey == toKey)
1059+ if any messages (\m -> m.messageId == messageId && m.toKey == toKey)
1055− || any ackedIds (\a -> a.messageId == messageId && a.toKey == toKey))
1060+ || any ackedIds (\a -> a.messageId == messageId && a.toKey == toKey)
1056− let shouldInsert = valid && not alreadyExists
1061+ then yield {pr: pr, ok: True {}}
1057− && countMessagesFor messages toKey t < maxMessagesPerRecipient
1062+ else if countMessagesFor messages toKey t >= maxMessagesPerRecipient
1058− set *seqCounter = if shouldInsert then seqTs else seqCounter
1063+ then yield {pr: pr, ok: False {}}
1059− set *messages = if shouldInsert
1064+ else do
1065+ seqCounter <- *seqCounter
1066+ let seqTs = nextSeqTs t seqCounter
1067+ set *seqCounter = seqTs
1060− then union messages
1068+ set *messages = union messages
1061− [{messageId: messageId, toKey: toKey, fromKey: fromKey,
1069+ [{messageId: messageId, toKey: toKey, fromKey: fromKey,
1062− encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
1070+ encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
1063− else messages
1064− yield {pr: pr, ok: shouldInsert || alreadyExists}
1071+ yield {pr: pr, ok: True {}}
1065 forkBroadcastOffline t result.pr
1072 forkBroadcastOffline t result.pr
1066 yield (respond {ok: result.ok})
1073 yield (respond {ok: result.ok})
1067
1074