on master help sign in
Improve server.knot: add GossipUnknown variant, simplify with comprehensions, clarify logic

- Add GossipUnknown variant to handle unknown gossip event types explicitly instead of silent fallback - Replace fold-based counting with count + comprehensions in countMessagesFor and containsMultipleColons - Remove unused FromJSON MessageStatus implementation (outgoing-only type) - Simplify LookupPresence server selection: separate local check from remote fallback fold - Remove offlineEntries intermediate variable in RecvGossip by using isGossipOffline directly

commitb4f9f43ea34cfb4eaf4961b16ccb789c9e538f70 authoriko <mail@iko.soy> dateFri 10 Apr 2026, 21:09 (6 months ago) parenta5a5e7c
27 files changed+27−61
client/App.fs+3−3 client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.dll client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.pdb client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/_CodeSignature/CodeResources+4−4 client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.dll client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.pdb client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/_CodeSignature/CodeResources+2−2 client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.AssemblyInfo.fs+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.fsproj.FileListAbsolute.txt−23 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.sourcelink.json+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/refint/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfo.fs+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.sourcelink.json+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/refint/Skrepka.dll server.knot+12−23
client/App.fs+3−3
@@ -49,7 +49,7 @@ module App =
4949 type ConnStatus =
5050 | Offline
5151 | Connecting
52− | Online of Session
52+ | Online of url: string * token: string
5353
5454 type AuthState =
5555 | NoIdentity
@@ -160,7 +160,7 @@ module App =
160160
161161 let private trySession model =
162162 match model.Auth with
163− | Identified(_, Online session) -> Some session
163+ | Identified(id, Online(url, token)) -> Some { Url = url; Token = token; Identity = id }
164164 | _ -> None
165165
166166 let private setConn status model =
@@ -225,7 +225,7 @@ module App =
225225 match model.Auth with
226226 | Identified(id, Connecting) ->
227227 let session = { Url = model.ServerUrl; Token = token; Identity = id }
228− { model with Auth = Identified(id, Online session); Page = Conversations; Error = None },
228+ { model with Auth = Identified(id, Online(model.ServerUrl, token)); Page = Conversations; Error = None },
229229 [ CmdPoll(session, model.PollCursor) ]
230230 | _ -> model, []
231231
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
@@ -166,11 +166,11 @@
166166 </data>
167167 <key>Skrepka.dll</key>
168168 <data>
169− XZqIgqY1iuk+FHTMZbQg6H06BPc=
169+ gaFYhy31V7Yzc0za1Sj1puG03t0=
170170 </data>
171171 <key>Skrepka.pdb</key>
172172 <data>
173− jGsllzUykGPLD645ovFAhjzyj0U=
173+ CJPccXCHHBmb+U3gBGs9lyyHKkw=
174174 </data>
175175 <key>Sodium.Core.dll</key>
176176 <data>
@@ -1393,14 +1393,14 @@
13931393 <dict>
13941394 <key>hash2</key>
13951395 <data>
1396− bxgl7DOK4AVvfqG7HqK1vCim2a91AGyHoW/pw+rbnjQ=
1396+ /REP/HoHjlxEG4EILkQsxLRTzNPyUb0OPrgeH7X+ysg=
13971397 </data>
13981398 </dict>
13991399 <key>Skrepka.pdb</key>
14001400 <dict>
14011401 <key>hash2</key>
14021402 <data>
1403− 3Fifs0NPR+poxKmLRf5cxvJCm1fk9qFg9gOZSl1VkC0=
1403+ ZbgMQL3trkYdSHBJ4m83nCenAnLY6W3xoI3w1+2M1xc=
14041404 </data>
14051405 </dict>
14061406 <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
@@ -251,14 +251,14 @@
251251 <dict>
252252 <key>hash2</key>
253253 <data>
254− sJGkuR5gxMR4CxhoKVU1LJTdCM3P3wmaAaPscSiCdwk=
254+ ZDY0RMPXDKaTDBgy9myo16G9HTQ00CFMBnGOxyxo8IQ=
255255 </data>
256256 </dict>
257257 <key>MonoBundle/Skrepka.pdb</key>
258258 <dict>
259259 <key>hash2</key>
260260 <data>
261− ANJi//INYTZtSEEDMIzRTGBB9feNGEHg+ZFulqWLaDc=
261+ sA8vqaz6By9kGWnIhXN4vmv2saJVvZVG4L5qa8GAqxE=
262262 </data>
263263 </dict>
264264 <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
1010[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
1111[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
1212[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+e3148dde3e9c36905f2c3f85861acc4776bc1da2")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+a5a5e7c5bac92a6f1d566920ab29663bfc3aad5f")>]
1414[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
1515[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
1616[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−6df29f3db280d4b4f3321357f916fe7e6dceca5ce10d790d3c17f749db0009ea
1+3de6148967a5feacb10209c6b84280448e0d8b2682259d470ffc1bcb59c62da5
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−23
@@ -334,26 +334,3 @@
334334/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.genruntimeconfig.cache
335335/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll
336336/Users/iko/Developer/skrepka/client/bin/Debug/net10.0-ios/iossimulator-arm64/LiteDB.dll
337−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.stamp
338−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.MauiControls.stamp
339−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/FSharp.Core.stamp
340−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/LiteDB.stamp
341−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.Abstractions.stamp
342−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.stamp
343−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.Abstractions.stamp
344−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.stamp
345−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Diagnostics.Abstractions.stamp
346−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.FileProviders.Abstractions.stamp
347−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Hosting.Abstractions.stamp
348−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.Abstractions.stamp
349−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.stamp
350−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Options.stamp
351−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Primitives.stamp
352−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Compatibility.stamp
353−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.stamp
354−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Xaml.stamp
355−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.stamp
356−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Essentials.stamp
357−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Graphics.stamp
358−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Sodium.Core.stamp
359−/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/e3148dde3e9c36905f2c3f85861acc4776bc1da2/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/a5a5e7c5bac92a6f1d566920ab29663bfc3aad5f/*"}}
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
1010[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
1111[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
1212[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+e3148dde3e9c36905f2c3f85861acc4776bc1da2")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+a5a5e7c5bac92a6f1d566920ab29663bfc3aad5f")>]
1414[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
1515[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
1616[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−edd6f79032ad5ae904eb2e4839e47e18b6506d82914ddfc23a23c0b225656098
1+ffbdef438cc5faa5721c5d7ba13b119c013565b8f8cc89fcefa7cd626606ad86
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/e3148dde3e9c36905f2c3f85861acc4776bc1da2/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/a5a5e7c5bac92a6f1d566920ab29663bfc3aad5f/*"}}
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+12−23
@@ -50,21 +50,23 @@ federationEnabled = federationSecret != ""
5050
5151type PollEvent = {id: MessageIdHex, eventType: Text, payload: {encryptedBlob: BlobHex, fromKey: PubkeyHex, timestamp: Timestamp, toKey: PubkeyHex}}
5252
53−type GossipEventType = GossipOnline {} | GossipOffline {}
53+type GossipEventType = GossipOnline {} | GossipOffline {} | GossipUnknown {}
5454
5555impl ToJSON GossipEventType where
5656 toJson et = toJson (case et of
5757 GossipOnline {} -> "online"
58− GossipOffline {} -> "offline")
58+ GossipOffline {} -> "offline"
59+ GossipUnknown {} -> "unknown")
5960
6061impl FromJSON GossipEventType where
6162 parseJson t =
6263 let s : Text = parseJson t in
6364 if s == "online" then GossipOnline {}
6465 else if s == "offline" then GossipOffline {}
65− else GossipOffline {}
66+ else GossipUnknown {}
6667
6768isGossipOnline = \et -> case et of GossipOnline {} -> True {}; _ -> False {}
69+isGossipOffline = \et -> case et of GossipOffline {} -> True {}; _ -> False {}
6870
6971type GossipEvent = {eventType: GossipEventType, pubkey: PubkeyHex, server: ServerName, timestamp: Timestamp, ttl: Int<S>}
7072
@@ -80,16 +82,6 @@ impl ToJSON MessageStatus where
8082 MsFederated {} -> "federated"
8183 MsQueued {} -> "queued")
8284
83−impl FromJSON MessageStatus where
84− parseJson t =
85− let s : Text = parseJson t in
86− if s == "unauthorized" then MsUnauthorized {}
87− else if s == "rejected" then MsRejected {}
88− else if s == "delivered" then MsDelivered {}
89− else if s == "federated" then MsFederated {}
90− else if s == "queued" then MsQueued {}
91− else MsQueued {}
92−
9385type PubkeyHex = Text where \s -> isValidHex s && not (tooLong s maxPubkeyLen) && s == normalizeHex s
9486type BlobHex = Text where \s -> isValidHex s && not (tooLong s maxBlobLen) && s == normalizeHex s
9587type SignatureHex = Text where \s -> isValidHex s && not (tooLong s maxSignatureLen) && s == normalizeHex s
@@ -253,7 +245,7 @@ isCgnatRange = \s ->
253245 then True {}
254246 else False {}
255247
256−containsMultipleColons = \s -> fold (\n c -> if c == ":" then n + 1 else n) 0 (chars s) > 1
248+containsMultipleColons = \s -> count (do c <- chars s; where c == ":"; yield c) > 1
257249
258250hasOctalOctet = \s ->
259251 if take 1 s == "0" && isDigitChar (take 1 (drop 1 s)) && isAllDigitsOrDotsUntilColonOrEnd (drop 2 s) then True {}
@@ -507,7 +499,7 @@ capGossipPresence = \afterOffline appliedOnline trustedDeduped presence trustedO
507499 withinCapacity: False {}}
508500
509501countMessagesFor = \messages toKey t ->
510− fold (\n m -> if m.toKey == toKey && m.receivedAt >= t - messageMaxAge then n + 1 else n) 0 messages
502+ count (do m <- messages; where m.toKey == toKey && m.receivedAt >= t - messageMaxAge; yield m)
511503
512504
513505validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
@@ -908,11 +900,9 @@ serve = \req -> case req of
908900 p <- presence
909901 where p.pubkey == pubkey && p.expiresAt > t
910902 yield p
911− let foundServer = (fold (\acc p ->
912− if acc.server == serverHostN then acc
913− else if p.server == serverHostN then {server: serverHostN, best: 0}
914− else if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt}
915− else acc) {server: "", best: 0} valid).server
903+ let localOnline = any valid (\p -> p.server == serverHostN)
904+ let foundServer = if localOnline then serverHostN
905+ else (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
916906 yield {pr: pr, authorized: True {}, foundServer: foundServer}
917907 forkBroadcastOffline t result.pr
918908 yield (respond {online: result.foundServer != "", server: result.foundServer, authorized: result.authorized})
@@ -928,14 +918,13 @@ serve = \req -> case req of
928918 else do
929919 let allValidEntries = validGossipEvents events t
930920 let onlineEntries = do e <- allValidEntries; where isGossipOnline e.eventType; yield e
931− let offlineEntries = do e <- allValidEntries; where not (isGossipOnline e.eventType); yield e
932− let trustedOffline = do e <- offlineEntries; where e.server == fromServer; yield e
921+ let trustedOffline = do e <- allValidEntries; where isGossipOffline e.eventType && e.server == fromServer; yield e
933922 let filteredOnline = do
934923 e <- onlineEntries
935924 where none trustedOffline (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
936925 yield e
937926 let filteredOffline = do
938− e <- offlineEntries
927+ e <- trustedOffline
939928 where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
940929 yield e
941930 let deduped = mergeByPubkeyServer [] filteredOnline