on master help sign in
Simplify code and improve type safety: sum type AuthResult, validate gossip once, consolidate over-capacity logic, extract forEachBatch
79a607diko
client/ApiClient.fs+4−4
@@ -38,8 +38,8 @@ module ApiClient =
38 token
38 token
39 |> Option.iter (fun t -> request.Headers.Add("Authorization", $"Bearer {t}"))
39 |> Option.iter (fun t -> request.Headers.Add("Authorization", $"Bearer {t}"))
40
40
41− let! response = client.SendAsync(request) |> Async.AwaitTask
41+ let! response = client.SendAsync(request) |> awaitTask
42− let! text = response.Content.ReadAsStringAsync() |> Async.AwaitTask
42+ let! text = response.Content.ReadAsStringAsync() |> awaitTask
43 return JsonDocument.Parse(text)
43 return JsonDocument.Parse(text)
44 }
44 }
45
45
@@ -47,8 +47,8 @@ module ApiClient =
47 async {
47 async {
48 let request = new HttpRequestMessage(HttpMethod.Get, url)
48 let request = new HttpRequestMessage(HttpMethod.Get, url)
49 request.Headers.Add("Authorization", $"Bearer {token}")
49 request.Headers.Add("Authorization", $"Bearer {token}")
50− let! response = client.SendAsync(request) |> Async.AwaitTask
50+ let! response = client.SendAsync(request) |> awaitTask
51− let! text = response.Content.ReadAsStringAsync() |> Async.AwaitTask
51+ let! text = response.Content.ReadAsStringAsync() |> awaitTask
52 return JsonDocument.Parse(text)
52 return JsonDocument.Parse(text)
53 }
53 }
54
54
client/App.fs+17−25
@@ -27,6 +27,13 @@ module App =
27 Timestamp: int64
27 Timestamp: int64
28 MessageId: string }
28 MessageId: string }
29
29
30+ [<CLIMutable>]
31+ type TextEnvelope =
32+ { ``type``: string
33+ id: string
34+ timestamp: int64
35+ body: string }
36+
30 type Page =
37 type Page =
31 | Setup
38 | Setup
32 | Conversations
39 | Conversations
@@ -86,9 +93,7 @@ module App =
86 | SetNewPubkey of string
93 | SetNewPubkey of string
87 | SetNewNickname of string
94 | SetNewNickname of string
88 | DoSaveContact
95 | DoSaveContact
89− | DoDeleteContact of string
90 | PollResult of PollResultData
96 | PollResult of PollResultData
91− | DoPoll
92 | CopyPubKey
97 | CopyPubKey
93 | DismissError
98 | DismissError
94 | StateLoaded of LoadedState
99 | StateLoaded of LoadedState
@@ -242,11 +247,6 @@ module App =
242
247
243 model', cmds @ (if not incoming.IsEmpty then saveCmd model' else [])
248 model', cmds @ (if not incoming.IsEmpty then saveCmd model' else [])
244
249
245− | DoPoll ->
246− match trySession model with
247− | Some session -> model, [ CmdPoll(session, model.PollCursor) ]
248− | None -> model, []
249−
250 | SetNewPubkey pk -> { model with NewPubkey = pk }, []
250 | SetNewPubkey pk -> { model with NewPubkey = pk }, []
251 | SetNewNickname nn -> { model with NewNickname = nn }, []
251 | SetNewNickname nn -> { model with NewNickname = nn }, []
252
252
@@ -267,13 +267,6 @@ module App =
267 else
267 else
268 model, []
268 model, []
269
269
270− | DoDeleteContact pk ->
271− let model' =
272− { model with
273− Contacts = model.Contacts |> List.filter (fun c -> c.Pubkey <> pk) }
274−
275− model', saveCmd model'
276−
277 | CopyPubKey ->
270 | CopyPubKey ->
278 match model.Identity with
271 match model.Identity with
279 | Some id -> model, [ CmdCopyToClipboard id.PubKeyHex ]
272 | Some id -> model, [ CmdCopyToClipboard id.PubKeyHex ]
@@ -316,12 +309,11 @@ module App =
316
309
317 let private asyncCmd (op: Async<Msg>) : Cmd<Msg> =
310 let private asyncCmd (op: Async<Msg>) : Cmd<Msg> =
318 Cmd.ofEffect (fun dispatch ->
311 Cmd.ofEffect (fun dispatch ->
319− System.Threading.Tasks.Task.Run(Func<System.Threading.Tasks.Task>(fun () ->
312+ async {
320− task {
321− let! msg = Async.StartAsTask op
313+ let! msg = op
322− dispatch msg
314+ dispatch msg
323− }))
315+ }
324− |> ignore)
316+ |> Async.Start)
325
317
326 let private decryptEvent (privKey: byte[]) (payload: ApiClient.EventPayload) =
318 let private decryptEvent (privKey: byte[]) (payload: ApiClient.EventPayload) =
327 try
319 try
@@ -329,13 +321,13 @@ module App =
329
321
330 match Crypto.decrypt privKey blob with
322 match Crypto.decrypt privKey blob with
331 | Some(plaintext, senderHex) ->
323 | Some(plaintext, senderHex) ->
332− let pt = JsonDocument.Parse(plaintext).RootElement
324+ let envelope = JsonSerializer.Deserialize<TextEnvelope>(plaintext)
333
325
334− if pt.GetProperty("type").GetString() = "text" then
326+ if envelope.``type`` = "text" then
335 Ok { FromPubkey = senderHex
327 Ok { FromPubkey = senderHex
336− Body = pt.GetProperty("body").GetString()
328+ Body = envelope.body
337 Timestamp = payload.Timestamp
329 Timestamp = payload.Timestamp
338− MessageId = pt.GetProperty("id").GetString() }
330+ MessageId = envelope.id }
339 else
331 else
340 Error "unknown type"
332 Error "unknown type"
341 | None -> Error "decrypt failed"
333 | None -> Error "decrypt failed"
@@ -369,7 +361,7 @@ module App =
369 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
361 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
370
362
371 let payload =
363 let payload =
372− JsonSerializer.Serialize({| ``type`` = "text"; id = messageId; timestamp = ts; body = text |})
364+ JsonSerializer.Serialize({ ``type`` = "text"; id = messageId; timestamp = ts; body = text }: TextEnvelope)
373
365
374 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
366 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
375 let blobHex = Crypto.toHex blob
367 let blobHex = Crypto.toHex blob
client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.pdb

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/_CodeSignature/CodeResources+4−4
@@ -162,11 +162,11 @@
162 </data>
162 </data>
163 <key>Skrepka.dll</key>
163 <key>Skrepka.dll</key>
164 <data>
164 <data>
165− sPcmYumCi7ODANv0qBWzcUKeaPI=
165+ +r8/McvWcZyDH15Tlxw+A2YZSqA=
166 </data>
166 </data>
167 <key>Skrepka.pdb</key>
167 <key>Skrepka.pdb</key>
168 <data>
168 <data>
169− TF5b1I6J39w2Dj1Wrqug0/yg9qg=
169+ 7zDN9fLf1e4bib8C9qjD5OXl3h8=
170 </data>
170 </data>
171 <key>Sodium.Core.dll</key>
171 <key>Sodium.Core.dll</key>
172 <data>
172 <data>
@@ -1382,14 +1382,14 @@
1382 <dict>
1382 <dict>
1383 <key>hash2</key>
1383 <key>hash2</key>
1384 <data>
1384 <data>
1385− BjaEjWa8QEFUOCVL1Ng+wnOYAZURekP8Q+FOVrnOCPQ=
1385+ eu/QLEnyJRtXlclZnchcx4K7yYLiVH8cqgv9VmdInys=
1386 </data>
1386 </data>
1387 </dict>
1387 </dict>
1388 <key>Skrepka.pdb</key>
1388 <key>Skrepka.pdb</key>
1389 <dict>
1389 <dict>
1390 <key>hash2</key>
1390 <key>hash2</key>
1391 <data>
1391 <data>
1392− PCHpD3YLFr5j0LMCBnow74wXEPFzHYve3q9OnCr7KSE=
1392+ pw4SQcQwk/RuJxp+aK9kDVeLoqstIlHDM6TY71IG6F0=
1393 </data>
1393 </data>
1394 </dict>
1394 </dict>
1395 <key>Sodium.Core.dll</key>
1395 <key>Sodium.Core.dll</key>
client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb

Binary file not shown.

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− J0e0G+5VLninqP1T7boUEWU/O/eykbUdWGzOK8RH4/M=
243+ YxPtcfpGxa+IKDOrDbOsu1NtXsc5daoFGZDbf16at/Q=
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− cb32M8TckGOvsdyO5UBJyRy7PLwXN2rTWF2p7NOSjQc=
250+ DyrAjXWRy1gvOE/NF4Imut7V+kKP3kTubqBKbjn9eik=
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-ios/iossimulator-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+b86f18ac7bc1810c038cac76bbaa8d60a06f932b")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+e6337c83d5e6108acdc6927de577a601ea6949b4")>]
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-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−1073aae7a8bfac91f0bc132ade6bddfa78bd27e8884b1abd921d1a371aa4b39f
1+febc92d682ab55304ab763b41325b2810feafbce3abb3916e01645680f754010
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.fsproj.FileListAbsolute.txt+22
@@ -333,3 +333,25 @@
333/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb
333/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb
334/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.genruntimeconfig.cache
334/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.genruntimeconfig.cache
335/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll
335/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll
336+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.stamp
337+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.MauiControls.stamp
338+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/FSharp.Core.stamp
339+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.Abstractions.stamp
340+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.stamp
341+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.Abstractions.stamp
342+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.stamp
343+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Diagnostics.Abstractions.stamp
344+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.FileProviders.Abstractions.stamp
345+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Hosting.Abstractions.stamp
346+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.Abstractions.stamp
347+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.stamp
348+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Options.stamp
349+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Primitives.stamp
350+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Compatibility.stamp
351+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.stamp
352+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Xaml.stamp
353+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.stamp
354+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Essentials.stamp
355+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Graphics.stamp
356+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Sodium.Core.stamp
357+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/unpack/Microsoft.Maui.Essentials/BundleResource/xml/microsoft_maui_essentials_fileprovider_file_paths.xml
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.sourcelink.json+1−1
@@ -1 +1 @@
1−{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/b86f18ac7bc1810c038cac76bbaa8d60a06f932b/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/e6337c83d5e6108acdc6927de577a601ea6949b4/*"}}
client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll

Binary file not shown.

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

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+b86f18ac7bc1810c038cac76bbaa8d60a06f932b")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+e6337c83d5e6108acdc6927de577a601ea6949b4")>]
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−65f3a65e8aad333c4ce67904855fcf84ff492a316f7f53d5fb7bac25600d9db6
1+786da36e5c8bb4572c2194d3d1b64b25fa7ad4d4e4fbac83233c30ec5fd7b804
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/b86f18ac7bc1810c038cac76bbaa8d60a06f932b/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/e6337c83d5e6108acdc6927de577a601ea6949b4/*"}}
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+146−158
@@ -68,7 +68,7 @@ type Message = {messageId: MessageIdHex, toKey: PubkeyHex, fromKey: PubkeyHex, e
68type PresenceEntry = {pubkey: PubkeyHex, server: Text, expiresAt: Timestamp, timestamp: Timestamp}
68type PresenceEntry = {pubkey: PubkeyHex, server: Text, expiresAt: Timestamp, timestamp: Timestamp}
69type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, fromKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp}
69type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, fromKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp}
70type AckedEntry = {messageId: MessageIdHex, toKey: PubkeyHex, ackedAt: Timestamp, originServer: ServerName}
70type AckedEntry = {messageId: MessageIdHex, toKey: PubkeyHex, ackedAt: Timestamp, originServer: ServerName}
71−type AuthResult = {authorized: Bool, pubkey: Text}
71+type AuthResult = Authorized {pubkey: PubkeyHex} | Unauthorized {}
72type PruneResult = {expiredLocal: [PresenceEntry], peerServers: [Text]}
72type PruneResult = {expiredLocal: [PresenceEntry], peerServers: [Text]}
73
73
74
74
@@ -149,9 +149,6 @@ headOf = \items -> fold (\acc x -> case acc of
149 Nothing {} -> Just {value: x}
149 Nothing {} -> Just {value: x}
150 _ -> acc) Nothing {} items
150 _ -> acc) Nothing {} items
151
151
152−msToS : Int<Ms> -> Int<S>
153−msToS = \x -> x / 1000
154−
155hexDigit = \n ->
152hexDigit = \n ->
156 let n = if n < 0 then 0 else if n > 15 then 15 else n in
153 let n = if n < 0 then 0 else if n > 15 then 15 else n in
157 if n == 0 then "0" else if n == 1 then "1" else if n == 2 then "2" else if n == 3 then "3"
154 if n == 0 then "0" else if n == 1 then "1" else if n == 2 then "2" else if n == 3 then "3"
@@ -366,12 +363,12 @@ isBadServerName = \s ->
366authenticateSession : [Session] -> Text -> Timestamp -> AuthResult
363authenticateSession : [Session] -> Text -> Timestamp -> AuthResult
367authenticateSession = \sessions token t ->
364authenticateSession = \sessions token t ->
368 fold (\acc s ->
365 fold (\acc s ->
369− if acc.authorized then acc
366+ case acc of
370− else
367+ Authorized _ -> acc
368+ Unauthorized {} ->
371− let matches = safeCompare s.token token in
369+ if safeCompare s.token token && s.expiresAt > t
372− if matches && s.expiresAt > t
373− then {authorized: True {}, pubkey: s.pubkey}
370+ then Authorized {pubkey: s.pubkey}
374− else acc) {authorized: False {}, pubkey: ""} sessions
371+ else acc) Unauthorized {} sessions
375
372
376extractToken = \authHeader ->
373extractToken = \authHeader ->
377 if toLower (take 7 authHeader) == "bearer "
374 if toLower (take 7 authHeader) == "bearer "
@@ -495,10 +492,9 @@ countMessagesFor = \messages toKey t ->
495 yield m)
492 yield m)
496
493
497
494
498−validGossipEvents : [GossipEvent] -> Timestamp -> GossipEventType -> [ValidGossipEntry]
495+validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
499−validGossipEvents = \events t eventType -> do
496+validGossipEvents = \events t -> do
500 e <- events
497 e <- events
501− where e.eventType == eventType
502 where not (tooLong e.pubkey maxPubkeyLen)
498 where not (tooLong e.pubkey maxPubkeyLen)
503 where not (tooLong e.server maxServerNameLen)
499 where not (tooLong e.server maxServerNameLen)
504 where not (isBadServerName e.server)
500 where not (isBadServerName e.server)
@@ -507,8 +503,9 @@ validGossipEvents = \events t eventType -> do
507 where not (server == serverHostN)
503 where not (server == serverHostN)
508 where e.timestamp <= t + clockSkewTolerance
504 where e.timestamp <= t + clockSkewTolerance
509 let ttl = clampTtl e.ttl
505 let ttl = clampTtl e.ttl
510− where e.timestamp + ttl * 1000 > t
506+ let expiresAt = e.timestamp + ttl * 1000
507+ where expiresAt > t
511− yield {eventType: eventType, pubkey: normalizeHex e.pubkey, server: server, expiresAt: e.timestamp + ttl * 1000, timestamp: e.timestamp, ttl: ttl}
508+ yield {eventType: e.eventType, pubkey: normalizeHex e.pubkey, server: server, expiresAt: expiresAt, timestamp: e.timestamp, ttl: ttl}
512
509
513sendToEach : [Text] -> (Text -> (a {})) -> a {}
510sendToEach : [Text] -> (Text -> (a {})) -> a {}
514sendToEach = \servers action ->
511sendToEach = \servers action ->
@@ -562,17 +559,21 @@ maybePrune = \t -> do
562 set *lastPrunedAt = t
559 set *lastPrunedAt = t
563 pruneExpired t
560 pruneExpired t
564
561
562+forEachBatch = \items batchSize process ->
563+ if count items == 0 then yield {}
564+ else do
565+ let batch = takeN items batchSize
566+ let rest = diff items batch
567+ process batch
568+ forEachBatch rest batchSize process
569+
565sendBatchedGossipToServer : Text -> [GossipEvent] -> IO {network} {}
570sendBatchedGossipToServer : Text -> [GossipEvent] -> IO {network} {}
566sendBatchedGossipToServer = \server allEvents ->
571sendBatchedGossipToServer = \server allEvents ->
567− if count allEvents == 0
572+ forEachBatch allEvents maxGossipEvents (\batch -> do
568− then yield {}
569− else do
570− let batch = takeN allEvents maxGossipEvents
571− let rest = diff allEvents batch
572− _ <- fetch (federationScheme ++ server) (RecvGossip {
573+ _ <- fetch (federationScheme ++ server) (RecvGossip {
573− authorization: "Bearer " ++ federationSecret,
574+ authorization: "Bearer " ++ federationSecret,
574− events: batch, hopsRemaining: 2, fromServer: serverHostN})
575+ events: batch, hopsRemaining: 2, fromServer: serverHostN})
575− sendBatchedGossipToServer server rest
576+ yield {})
576
577
577sendBatchedGossip : [Text] -> [GossipEvent] -> IO {network} {}
578sendBatchedGossip : [Text] -> [GossipEvent] -> IO {network} {}
578sendBatchedGossip = \peerServers allEvents ->
579sendBatchedGossip = \peerServers allEvents ->
@@ -628,25 +629,20 @@ retryPendingForwards = \pending -> do
628
629
629sendBatchedAcksToServer : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, | a}] -> Text -> IO {network} {}
630sendBatchedAcksToServer : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, | a}] -> Text -> IO {network} {}
630sendBatchedAcksToServer = \serverAcks server ->
631sendBatchedAcksToServer = \serverAcks server ->
631− if count serverAcks == 0
632− then yield {}
633− else do
634− let batch = takeN serverAcks maxMessageIds
632+ forEachBatch serverAcks maxMessageIds (\batch -> do
635− let rest = diff serverAcks batch
636− result <- fetch (federationScheme ++ server) (FederationAck {
633+ result <- fetch (federationScheme ++ server) (FederationAck {
637− authorization: "Bearer " ++ federationSecret,
634+ authorization: "Bearer " ++ federationSecret,
638− messageIds: (do a <- batch; yield a.messageId),
635+ messageIds: (do a <- batch; yield a.messageId),
639− originServer: server, fromServer: serverHostN})
636+ originServer: server, fromServer: serverHostN})
640− case result of
637+ case result of
641− Ok _ ->
638+ Ok _ ->
642− atomic do
639+ atomic do
643− ackedIds <- *ackedIds
640+ ackedIds <- *ackedIds
644− set *ackedIds = do
641+ set *ackedIds = do
645− a <- ackedIds
642+ a <- ackedIds
646− where not (a.originServer == server && any batch (\sa -> sa.messageId == a.messageId && sa.toKey == a.toKey))
643+ where not (a.originServer == server && any batch (\sa -> sa.messageId == a.messageId && sa.toKey == a.toKey))
647− yield a
644+ yield a
648− Err _ -> yield {}
645+ Err _ -> yield {})
649− sendBatchedAcksToServer rest server
650
646
651sendAcksAndClean : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, | a}] -> [Text] -> IO {network} {}
647sendAcksAndClean : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, | a}] -> [Text] -> IO {network} {}
652sendAcksAndClean = \pendingAcks servers ->
648sendAcksAndClean = \pendingAcks servers ->
@@ -676,28 +672,30 @@ pollOnce = \token cursor -> do
676 t <- now
672 t <- now
677 atomic do
673 atomic do
678 sessions <- *sessions
674 sessions <- *sessions
679− messages <- *messages
680 seqCounter <- *seqCounter
675 seqCounter <- *seqCounter
681 let auth = authenticateSession sessions token t
676 let auth = authenticateSession sessions token t
682 let cursorCeil = if seqCounter > t then seqCounter else t
677 let cursorCeil = if seqCounter > t then seqCounter else t
683 let clampedCursor = if cursor < 0 then 0 else if cursor > cursorCeil then cursorCeil else cursor
678 let clampedCursor = if cursor < 0 then 0 else if cursor > cursorCeil then cursorCeil else cursor
684− let matchingMsgs = if not auth.authorized then []
679+ case auth of
685− else do
680+ Unauthorized {} ->
681+ yield {events: [], cursor: -1, authorized: False {}}
682+ Authorized {pubkey} -> do
683+ messages <- *messages
684+ let matchingMsgs = do
686− m <- messages
685+ m <- messages
687− where m.toKey == auth.pubkey
686+ where m.toKey == pubkey
688− where m.receivedAt >= t - messageMaxAge
687+ where m.receivedAt >= t - messageMaxAge
689− where m.receivedAt > clampedCursor
688+ where m.receivedAt > clampedCursor
690− yield m
689+ yield m
691− let limitedMsgs = boundedSmallest matchingMsgs maxPollEvents (\m -> m.receivedAt)
690+ let limitedMsgs = boundedSmallest matchingMsgs maxPollEvents (\m -> m.receivedAt)
692− let evts = do
691+ let evts = do
693− m <- limitedMsgs
692+ m <- limitedMsgs
694− yield {id: m.messageId, eventType: "message",
693+ yield {id: m.messageId, eventType: "message",
695− payload: {toKey: m.toKey, fromKey: m.fromKey,
694+ payload: {toKey: m.toKey, fromKey: m.fromKey,
696− encryptedBlob: m.encryptedBlob,
695+ encryptedBlob: m.encryptedBlob,
697− timestamp: m.timestamp}}
696+ timestamp: m.timestamp}}
698− let lastReceivedAt = fold (\acc m -> if m.receivedAt > acc then m.receivedAt else acc) clampedCursor limitedMsgs
697+ let lastReceivedAt = fold (\acc m -> if m.receivedAt > acc then m.receivedAt else acc) clampedCursor limitedMsgs
699− let newCursor = if not auth.authorized then -1 else lastReceivedAt
700− yield {events: evts, cursor: newCursor, authorized: auth.authorized}
698+ yield {events: evts, cursor: lastReceivedAt, authorized: True {}}
701
699
702pollLoop : Text -> Int -> Int -> IO {clock} {authorized: Bool, cursor: Int, events: [PollEvent]}
700pollLoop : Text -> Int -> Int -> IO {clock} {authorized: Bool, cursor: Int, events: [PollEvent]}
703pollLoop = \token cursor deadline -> do
701pollLoop = \token cursor deadline -> do
@@ -816,38 +814,37 @@ serve = \req -> case req of
816 result <- atomic do
814 result <- atomic do
817 pr <- maybePrune t
815 pr <- maybePrune t
818 sessions <- *sessions
816 sessions <- *sessions
819− messages <- *messages
820− presence <- *presence
821 let auth = authenticateSession sessions (extractToken authorization) t
817 let auth = authenticateSession sessions (extractToken authorization) t
822− let fromKey = auth.pubkey
818+ case auth of
823− let authorized = auth.authorized
819+ Unauthorized {} ->
820+ yield {status: "unauthorized", shouldInsert: False {}, remoteServers: [], fromKey: "", expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, forwardsAtCapacity: False {}}
821+ Authorized {pubkey: fromKey} -> do
822+ messages <- *messages
823+ presence <- *presence
824− seqCounter <- *seqCounter
824+ seqCounter <- *seqCounter
825− let seqTs = nextSeqTs t seqCounter
825+ let seqTs = nextSeqTs t seqCounter
826− let shouldInsert = authorized && countMessagesFor messages to t < maxMessagesPerRecipient
826+ let shouldInsert = countMessagesFor messages to t < maxMessagesPerRecipient
827− set *seqCounter = if shouldInsert then seqTs else seqCounter
827+ set *seqCounter = if shouldInsert then seqTs else seqCounter
828− set *messages = if shouldInsert
828+ set *messages = if shouldInsert
829− then union messages
829+ then union messages
830− [{messageId: msgId, toKey: to, fromKey: fromKey,
830+ [{messageId: msgId, toKey: to, fromKey: fromKey,
831− encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
831+ encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
832− else messages
832+ else messages
833− let toPresence = if authorized
834− then do p <- presence; where p.pubkey == to; where p.expiresAt > t; yield p
833+ let toPresence = do p <- presence; where p.pubkey == to; where p.expiresAt > t; yield p
835− else []
836− let localOnline = any toPresence (\p -> p.server == serverHostN)
834+ let localOnline = any toPresence (\p -> p.server == serverHostN)
837− let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
835+ let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
838− forwards <- *forwards
836+ forwards <- *forwards
839− let forwardsAtCapacity = count forwards >= maxForwardsEntries
837+ let forwardsAtCapacity = count forwards >= maxForwardsEntries
840− let status = if not authorized then "unauthorized"
841− else if not shouldInsert then "rejected"
838+ let status = if not shouldInsert then "rejected"
842− else if localOnline then "delivered"
839+ else if localOnline then "delivered"
843− else if count remoteServers > 0 && federationEnabled && not forwardsAtCapacity then "federated"
840+ else if count remoteServers > 0 && federationEnabled && not forwardsAtCapacity then "federated"
844− else "queued"
841+ else "queued"
845− let forwardSlots = maxForwardsEntries - count forwards
842+ let forwardSlots = maxForwardsEntries - count forwards
846− let forwardServers = takeN remoteServers forwardSlots
843+ let forwardServers = takeN remoteServers forwardSlots
847− set *forwards = if shouldInsert && not forwardsAtCapacity && count forwardServers > 0 && federationEnabled
844+ set *forwards = if shouldInsert && not forwardsAtCapacity && count forwardServers > 0 && federationEnabled
848− then union forwards (do s <- forwardServers; yield {messageId: msgId, toServer: s, createdAt: t, toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob, timestamp: clampedTs})
845+ then union forwards (do s <- forwardServers; yield {messageId: msgId, toServer: s, createdAt: t, toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob, timestamp: clampedTs})
849− else forwards
846+ else forwards
850− yield {status: status, shouldInsert: shouldInsert, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, forwardsAtCapacity: forwardsAtCapacity}
847+ yield {status: status, shouldInsert: shouldInsert, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, forwardsAtCapacity: forwardsAtCapacity}
851 fork (do
848 fork (do
852 if count result.remoteServers > 0 && result.shouldInsert && federationEnabled
849 if count result.remoteServers > 0 && result.shouldInsert && federationEnabled
853 then sendToEach result.remoteServers (\server -> do
850 then sendToEach result.remoteServers (\server -> do
@@ -872,29 +869,32 @@ serve = \req -> case req of
872 result <- atomic do
869 result <- atomic do
873 pr <- maybePrune t
870 pr <- maybePrune t
874 sessions <- *sessions
871 sessions <- *sessions
875− messages <- *messages
876 let auth = authenticateSession sessions (extractToken authorization) t
872 let auth = authenticateSession sessions (extractToken authorization) t
877− let ackedMessages = if not auth.authorized then []
873+ case auth of
878− else do
874+ Unauthorized {} ->
875+ yield {ok: False {}, pendingAcks: [], expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, evictedEntries: []}
876+ Authorized {pubkey} -> do
877+ messages <- *messages
878+ let ackedMessages = do
879− m <- messages
879+ m <- messages
880− mid <- dedupedIds
880+ mid <- dedupedIds
881− where m.messageId == mid
881+ where m.messageId == mid
882− where m.toKey == auth.pubkey
882+ where m.toKey == pubkey
883− yield m
883+ yield m
884− set *messages = diff messages ackedMessages
884+ set *messages = diff messages ackedMessages
885− let ackedFederated = do
885+ let ackedFederated = do
886− m <- ackedMessages
886+ m <- ackedMessages
887− where isFederatedOrigin m.originServer
887+ where isFederatedOrigin m.originServer
888− yield {messageId: m.messageId, toKey: m.toKey, ackedAt: t, originServer: m.originServer}
888+ yield {messageId: m.messageId, toKey: m.toKey, ackedAt: t, originServer: m.originServer}
889− ackedIds <- *ackedIds
889+ ackedIds <- *ackedIds
890− let combinedAckedIds = union ackedIds ackedFederated
890+ let combinedAckedIds = union ackedIds ackedFederated
891− let excessCount = count combinedAckedIds - maxAckedIds
891+ let excessCount = count combinedAckedIds - maxAckedIds
892− let afterEviction = if excessCount <= 0 then combinedAckedIds
892+ let afterEviction = if excessCount <= 0 then combinedAckedIds
893− else evictOldestAckedIds combinedAckedIds excessCount
893+ else evictOldestAckedIds combinedAckedIds excessCount
894− let evictedEntries = if excessCount <= 0 then []
894+ let evictedEntries = if excessCount <= 0 then []
895− else diff combinedAckedIds afterEviction
895+ else diff combinedAckedIds afterEviction
896− set *ackedIds = afterEviction
896+ set *ackedIds = afterEviction
897− yield {ok: auth.authorized, pendingAcks: ackedFederated, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, evictedEntries: evictedEntries}
897+ yield {ok: True {}, pendingAcks: ackedFederated, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, evictedEntries: evictedEntries}
898 let originServers = collectPeerServers result.pendingAcks (\a -> a.originServer)
898 let originServers = collectPeerServers result.pendingAcks (\a -> a.originServer)
899 fork (do
899 fork (do
900 if result.ok
900 if result.ok
@@ -941,20 +941,21 @@ serve = \req -> case req of
941 result <- atomic do
941 result <- atomic do
942 pr <- maybePrune t
942 pr <- maybePrune t
943 sessions <- *sessions
943 sessions <- *sessions
944− presence <- *presence
945 let auth = authenticateSession sessions (extractToken authorization) t
944 let auth = authenticateSession sessions (extractToken authorization) t
946− let authorized = auth.authorized
945+ case auth of
946+ Unauthorized {} ->
947+ yield {foundServer: "", authorized: False {}, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
948+ Authorized _ -> do
949+ presence <- *presence
947− let valid = do
950+ let valid = do
948− p <- presence
951+ p <- presence
949− where p.pubkey == pubkey
952+ where p.pubkey == pubkey
950− where p.expiresAt > t
953+ where p.expiresAt > t
951− yield p
954+ yield p
952− let foundServer = if not authorized
953− then ""
954− else if any valid (\p -> p.server == serverHostN)
955+ let foundServer = if any valid (\p -> p.server == serverHostN)
955− then serverHostN
956+ then serverHostN
956− else (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
957+ else (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
957− yield {foundServer: foundServer, authorized: authorized, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
958+ yield {foundServer: foundServer, authorized: True {}, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
958 forkBroadcastOffline t result
959 forkBroadcastOffline t result
959 yield (respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized})
960 yield (respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized})
960
961
@@ -964,8 +965,9 @@ serve = \req -> case req of
964 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
965 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
965 let fromServer = normalizeServerName fromServer
966 let fromServer = normalizeServerName fromServer
966 let validRequest = authorized && cappedHops >= 1 && not (fromServer == serverHostN) && count events <= maxGossipEvents
967 let validRequest = authorized && cappedHops >= 1 && not (fromServer == serverHostN) && count events <= maxGossipEvents
967− let onlineEntries = if validRequest then validGossipEvents events t "online" else []
968+ let allValidEntries = if validRequest then validGossipEvents events t else []
968− let offlineEntries = if validRequest then validGossipEvents events t "offline" else []
969+ let onlineEntries = do e <- allValidEntries; where e.eventType == "online"; yield e
970+ let offlineEntries = do e <- allValidEntries; where e.eventType == "offline"; yield e
969 let filteredOnline = do
971 let filteredOnline = do
970 e <- onlineEntries
972 e <- onlineEntries
971 where none offlineEntries (\o -> o.server == fromServer && samePubkeyServer o e && o.timestamp > e.timestamp)
973 where none offlineEntries (\o -> o.server == fromServer && samePubkeyServer o e && o.timestamp > e.timestamp)
@@ -1000,37 +1002,23 @@ serve = \req -> case req of
1000 yield {pubkey: k.pubkey, server: k.server}
1002 yield {pubkey: k.pubkey, server: k.server}
1001 let afterOffline = removeOffline merged offlineOnlyKeys trustedOffline t
1003 let afterOffline = removeOffline merged offlineOnlyKeys trustedOffline t
1002 let withinCapacity = count afterOffline <= maxPresenceEntries
1004 let withinCapacity = count afterOffline <= maxPresenceEntries
1005+ let capResult = if withinCapacity
1006+ then {finalPresence: afterOffline, appliedOnline: appliedOnline}
1007+ else
1003− let updatesOnly = do e <- trustedDeduped; where any presence (\p -> samePubkeyServer p e); yield e
1008+ let updatesOnly = do e <- trustedDeduped; where any presence (\p -> samePubkeyServer p e); yield e in
1004− let updatesOnlyMerged = if withinCapacity then afterOffline
1005− else mergeByPubkeyServer presence updatesOnly
1009+ let updatesOnlyMerged = mergeByPubkeyServer presence updatesOnly in
1006− let updatesOnlyAfterOffline = if withinCapacity then afterOffline
1007− else removeOffline updatesOnlyMerged offlineOnlyKeys trustedOffline t
1010+ let updatesOnlyAfterOffline = removeOffline updatesOnlyMerged offlineOnlyKeys trustedOffline t in
1008− let reclaimCapacity = if withinCapacity then 0 else maxPresenceEntries - count updatesOnlyAfterOffline
1011+ let reclaimSlots = maxPresenceEntries - count updatesOnlyAfterOffline in
1009− let newEntries = if withinCapacity then []
1010− else do
1011− e <- trustedDeduped
1012− where none presence (\p -> samePubkeyServer p e)
1013− where none updatesOnlyAfterOffline (\p -> samePubkeyServer p e)
1012+ let newEntries = do e <- trustedDeduped; where none presence (\p -> samePubkeyServer p e); where none updatesOnlyAfterOffline (\p -> samePubkeyServer p e); yield e in
1014− yield e
1015− let reclaimedEntries = if withinCapacity then []
1013+ let reclaimedEntries = takeN newEntries reclaimSlots in
1016− else takeN newEntries reclaimCapacity
1017− let afterReclaim = if withinCapacity then afterOffline
1018− else union updatesOnlyAfterOffline reclaimedEntries
1014+ {finalPresence: union updatesOnlyAfterOffline reclaimedEntries,
1019− let appliedOnlineUpdates = if withinCapacity then appliedOnline
1015+ appliedOnline: union
1020− else union
1021− (do
1022− e <- updatesOnly
1023− where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt)
1016+ (do e <- updatesOnly; where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt); yield {pubkey: e.pubkey, server: e.server})
1024− yield {pubkey: e.pubkey, server: e.server})
1025− (do
1026− e <- reclaimedEntries
1027− yield {pubkey: e.pubkey, server: e.server})
1017+ (do e <- reclaimedEntries; yield {pubkey: e.pubkey, server: e.server})}
1028− let finalPresence = if validRequest
1018+ let finalPresence = if validRequest then capResult.finalPresence else presence
1029− then if withinCapacity then afterOffline else afterReclaim
1030− else presence
1031 set *presence = finalPresence
1019 set *presence = finalPresence
1032 let gossipPeerServers = collectPeerServers finalPresence (\p -> p.server)
1020 let gossipPeerServers = collectPeerServers finalPresence (\p -> p.server)
1033− yield {appliedKeys: (if withinCapacity then union appliedOnline appliedOffline else union appliedOnlineUpdates appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
1021+ yield {appliedKeys: union capResult.appliedOnline appliedOffline, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
1034 let filteredOffline = do
1022 let filteredOffline = do
1035 e <- offlineEntries
1023 e <- offlineEntries
1036 where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
1024 where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)