on master help sign in
Simplify code and improve type safety: use stdlib for string operations, require normalized ServerName, type config durations with units, use PubkeyHex in GossipEvent
f4f8a00iko
client/ApiClient.fs+1−1
@@ -18,7 +18,7 @@ module ApiClient =
18 type MessageStatus = Delivered | Federated | Queued | Rejected | Unauthorized
18 type MessageStatus = Delivered | Federated | Queued | Rejected | Unauthorized
19
19
20 [<CLIMutable>]
20 [<CLIMutable>]
21− type private SendResult = { Status: MessageStatus; MessageId: string }
21+ type private SendResult = { Status: MessageStatus }
22
22
23 [<CLIMutable>]
23 [<CLIMutable>]
24 type PollResponse = { Cursor: int64; Events: PollEvent array }
24 type PollResponse = { Cursor: int64; Events: PollEvent array }
client/App.fs+5−10
@@ -21,7 +21,6 @@ module App =
21 type private TextEnvelope =
21 type private TextEnvelope =
22 { [<JsonPropertyName("type")>] Type: string
22 { [<JsonPropertyName("type")>] Type: string
23 [<JsonPropertyName("id")>] Id: string
23 [<JsonPropertyName("id")>] Id: string
24− [<JsonPropertyName("timestamp")>] Timestamp: int64
25 [<JsonPropertyName("body")>] Body: string }
24 [<JsonPropertyName("body")>] Body: string }
26
25
27 type Page =
26 type Page =
@@ -134,7 +133,7 @@ module App =
134 | NoIdentity -> model
133 | NoIdentity -> model
135
134
136 let private saveCmdMsg model =
135 let private saveCmdMsg model =
137− CmdSaveData { Contacts = model.Contacts; Messages = model.Messages; ServerUrl = model.ServerUrl; PollCursor = model.PollCursor }
136+ CmdSaveData { Contacts = model.Contacts; Messages = model.Messages; ServerUrl = Some model.ServerUrl; PollCursor = model.PollCursor }
138
137
139 // ── Init ──
138 // ── Init ──
140
139
@@ -246,7 +245,7 @@ module App =
246 Auth = Identified(identity, Connecting)
245 Auth = Identified(identity, Connecting)
247 Contacts = data.Contacts
246 Contacts = data.Contacts
248 Messages = data.Messages
247 Messages = data.Messages
249− ServerUrl = if data.ServerUrl <> "" then data.ServerUrl else model.ServerUrl
248+ ServerUrl = data.ServerUrl |> Option.defaultValue model.ServerUrl
250 PollCursor = data.PollCursor
249 PollCursor = data.PollCursor
251 Page = Conversations }
250 Page = Conversations }
252 model', [ CmdConnect(model'.ServerUrl, identity) ]
251 model', [ CmdConnect(model'.ServerUrl, identity) ]
@@ -303,10 +302,7 @@ module App =
303 try
302 try
304 let recipPub = Crypto.fromHex recipientHex
303 let recipPub = Crypto.fromHex recipientHex
305 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
304 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
306−
307− let payload =
308− JsonSerializer.Serialize({ Type = "text"; Id = messageId; Timestamp = ts; Body = text }: TextEnvelope)
305+ let payload = JsonSerializer.Serialize({ Type = "text"; Id = messageId; Body = text }: TextEnvelope)
309−
310 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
306 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
311 let! status = sendMessage session.Url session.Token recipientHex (Crypto.toHex blob) ts
307 let! status = sendMessage session.Url session.Token recipientHex (Crypto.toHex blob) ts
312
308
@@ -477,9 +473,8 @@ module App =
477
473
478 for c in model.Contacts do
474 for c in model.Contacts do
479 let preview =
475 let preview =
480− model.Messages
476+ messagesFor c.Pubkey model.Messages
481− |> Map.tryFind c.Pubkey
482− |> Option.bind List.tryLast
477+ |> List.tryLast
483 |> Option.map (fun m -> truncate 40 m.Body)
478 |> Option.map (fun m -> truncate 40 m.Body)
484 |> Option.defaultValue ""
479 |> Option.defaultValue ""
485
480
client/Store.fs+11−17
@@ -21,7 +21,7 @@ module Store =
21 type Data =
21 type Data =
22 { Contacts: Contact list
22 { Contacts: Contact list
23 Messages: Map<string, ChatMessage list>
23 Messages: Map<string, ChatMessage list>
24− ServerUrl: string
24+ ServerUrl: string option
25 PollCursor: int64 }
25 PollCursor: int64 }
26
26
27 [<CLIMutable>]
27 [<CLIMutable>]
@@ -53,24 +53,18 @@ module Store =
53 SecureStorage.Default.GetAsync("identity_privkey")
53 SecureStorage.Default.GetAsync("identity_privkey")
54 |> Async.AwaitTask
54 |> Async.AwaitTask
55
55
56− let! pubKeyHex =
57− SecureStorage.Default.GetAsync("identity_pubkey")
58− |> Async.AwaitTask
59−
60 return
56 return
57+ privKeyB64
61− Option.map2
58+ |> Option.ofObj
62− (fun pk pub ->
59+ |> Option.map (fun b64 ->
60+ let privKey = Convert.FromBase64String b64
63− { Crypto.Identity.PrivKey = Convert.FromBase64String pk
61+ { Crypto.Identity.PrivKey = privKey
64− Crypto.Identity.PubKeyHex = pub })
62+ Crypto.Identity.PubKeyHex = Crypto.toHex privKey.[32..63] })
65− (Option.ofObj privKeyB64)
66− (Option.ofObj pubKeyHex)
67 }
63 }
68
64
69 let saveIdentity (identity: Crypto.Identity) =
65 let saveIdentity (identity: Crypto.Identity) =
70− async {
71− do! SecureStorage.Default.SetAsync("identity_privkey", Convert.ToBase64String identity.PrivKey) |> Async.AwaitTask
66+ SecureStorage.Default.SetAsync("identity_privkey", Convert.ToBase64String identity.PrivKey)
72− do! SecureStorage.Default.SetAsync("identity_pubkey", identity.PubKeyHex) |> Async.AwaitTask
67+ |> Async.AwaitTask
73− }
74
68
75 let loadData () : Data option =
69 let loadData () : Data option =
76 try
70 try
@@ -91,7 +85,7 @@ module Store =
91
85
92 { Contacts = contacts
86 { Contacts = contacts
93 Messages = messages
87 Messages = messages
94− ServerUrl = settings.ServerUrl
88+ ServerUrl = if String.IsNullOrEmpty settings.ServerUrl then None else Some settings.ServerUrl
95 PollCursor = settings.PollCursor })
89 PollCursor = settings.PollCursor })
96 with _ ->
90 with _ ->
97 None
91 None
@@ -113,7 +107,7 @@ module Store =
113
107
114 settings.Upsert
108 settings.Upsert
115 { Id = "settings"
109 { Id = "settings"
116− ServerUrl = data.ServerUrl
110+ ServerUrl = data.ServerUrl |> Option.defaultValue ""
117 PollCursor = data.PollCursor }
111 PollCursor = data.PollCursor }
118 |> ignore
112 |> ignore
119 with _ ->
113 with _ ->
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+9f1d0504eb230b677ebf8681fd8a2150173ae8c9")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+c0cce11a1754909d3aa01c81e84df143a9bcd275")>]
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−7470222b83928946d2db18cc26617b6558d2063223e8ceebffe9053d7ef967f6
1+f6ed10664c2343267ee52965047bd52c638d641ef55e392d09d2476f8f4fbb69
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/9f1d0504eb230b677ebf8681fd8a2150173ae8c9/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/c0cce11a1754909d3aa01c81e84df143a9bcd275/*"}}
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+40−76
@@ -10,8 +10,10 @@ serverHost = "localhost:8080"
10federationSecret = "" -- must be set to enable federation
10federationSecret = "" -- must be set to enable federation
11maintenanceSecret = "" -- set to enable /maintenance/prune independently of federation
11maintenanceSecret = "" -- set to enable /maintenance/prune independently of federation
12federationScheme = "https://" -- scheme for federation HTTP calls
12federationScheme = "https://" -- scheme for federation HTTP calls
13+messageMaxAge : Int<Ms>
13−messageMaxAge = 2592000000 -- 30 days in milliseconds
14+messageMaxAge = 2592000000 -- 30 days
15+maxPresenceTtl : Int<S>
14−maxPresenceTtl = 3600 -- max presence TTL from gossip in seconds
16+maxPresenceTtl = 3600 -- max presence TTL from gossip
15maxPubkeyLen = 256
17maxPubkeyLen = 256
16maxBlobLen = 65536
18maxBlobLen = 65536
17maxSignatureLen = 256
19maxSignatureLen = 256
@@ -20,6 +22,7 @@ maxGossipEvents = 100
20maxPollEvents = 50
22maxPollEvents = 50
21maxPresenceEntries = 10000
23maxPresenceEntries = 10000
22maxMessagesPerRecipient = 500
24maxMessagesPerRecipient = 500
25+clockSkewTolerance : Int<Ms>
23clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
26clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
24maxForwardsEntries = 10000
27maxForwardsEntries = 10000
25maxServerNameLen = 128
28maxServerNameLen = 128
@@ -30,12 +33,16 @@ maxSessions = 10000
30maxAckedIds = 10000
33maxAckedIds = 10000
31maxChallengesPerKey = 5
34maxChallengesPerKey = 5
32maxSessionsPerKey = 10
35maxSessionsPerKey = 10
33−challengeExpiry = 60000 -- 1 minute in milliseconds
36+challengeExpiry : Int<Ms>
37+challengeExpiry = 60000 -- 1 minute
38+sessionExpiry : Int<Ms>
34−sessionExpiry = 3600000 -- 1 hour in milliseconds
39+sessionExpiry = 3600000 -- 1 hour
40+onlineGossipTtl : Int<S>
35−onlineGossipTtl = 3600 -- 1 hour in seconds
41+onlineGossipTtl = 3600 -- 1 hour
42+offlineGossipMinTtl : Int<S>
36−offlineGossipMinTtl = 60 -- minimum TTL for offline gossip events in seconds
43+offlineGossipMinTtl = 60 -- minimum TTL for offline gossip events
37maxForwardRetriesPerServer = 100
44maxForwardRetriesPerServer = 100
38−federationEnabled = not (federationSecret == "")
45+federationEnabled = federationSecret != ""
39
46
40-- ============================================
47-- ============================================
41-- Types
48-- Types
@@ -47,7 +54,7 @@ type PollEvent = {id: MessageIdHex, eventType: PollEventType, payload: {encrypte
47
54
48type GossipEventType = Text where \s -> s == "online" || s == "offline"
55type GossipEventType = Text where \s -> s == "online" || s == "offline"
49
56
50−type GossipEvent = {eventType: GossipEventType, pubkey: Text, server: Text, timestamp: Timestamp, ttl: Int<S>}
57+type GossipEvent = {eventType: GossipEventType, pubkey: PubkeyHex, server: Text, timestamp: Timestamp, ttl: Int<S>}
51
58
52type ValidGossipEntry = {eventType: GossipEventType, pubkey: PubkeyHex, server: ServerName, expiresAt: Timestamp, timestamp: Timestamp, ttl: Int<S>}
59type ValidGossipEntry = {eventType: GossipEventType, pubkey: PubkeyHex, server: ServerName, expiresAt: Timestamp, timestamp: Timestamp, ttl: Int<S>}
53
60
@@ -57,8 +64,8 @@ type PubkeyHex = Text where \s -> isValidHex s && not (tooLong s maxPubkeyLen) &
57type BlobHex = Text where \s -> isValidHex s && not (tooLong s maxBlobLen) && s == normalizeHex s
64type BlobHex = Text where \s -> isValidHex s && not (tooLong s maxBlobLen) && s == normalizeHex s
58type SignatureHex = Text where \s -> isValidHex s && not (tooLong s maxSignatureLen) && s == normalizeHex s
65type SignatureHex = Text where \s -> isValidHex s && not (tooLong s maxSignatureLen) && s == normalizeHex s
59type MessageIdHex = Text where \s -> isValidHex s && not (tooLong s maxMessageIdLen) && s == normalizeHex s
66type MessageIdHex = Text where \s -> isValidHex s && not (tooLong s maxMessageIdLen) && s == normalizeHex s
60−type ServerName = Text where \s -> not (tooLong s maxServerNameLen) && not (isBadServerName s)
67+type ServerName = Text where \s -> not (tooLong s maxServerNameLen) && not (isBadServerName s) && s == normalizeServerName s
61−type OptionalServerName = Text where \s -> s == "" || (not (tooLong s maxServerNameLen) && not (isBadServerName s))
68+type OptionalServerName = Text where \s -> s == "" || (not (tooLong s maxServerNameLen) && not (isBadServerName s) && s == normalizeServerName s)
62
69
63type Timestamp = Int<Ms>
70type Timestamp = Int<Ms>
64
71
@@ -156,12 +163,7 @@ headOf = \items -> fold (\acc x -> case acc of
156 Nothing {} -> Just {value: x}
163 Nothing {} -> Just {value: x}
157 _ -> acc) Nothing {} items
164 _ -> acc) Nothing {} items
158
165
159−hexDigit = \n ->
166+hexDigit = \n -> take 1 (drop (clamp 0 15 n) "0123456789abcdef")
160− let n = if n < 0 then 0 else if n > 15 then 15 else n in
161− if n == 0 then "0" else if n == 1 then "1" else if n == 2 then "2" else if n == 3 then "3"
162− else if n == 4 then "4" else if n == 5 then "5" else if n == 6 then "6" else if n == 7 then "7"
163− else if n == 8 then "8" else if n == 9 then "9" else if n == 10 then "a" else if n == 11 then "b"
164− else if n == 12 then "c" else if n == 13 then "d" else if n == 14 then "e" else "f"
165
167
166byteToHex = \n -> hexDigit (n / 16) ++ hexDigit (n - (n / 16) * 16)
168byteToHex = \n -> hexDigit (n / 16) ++ hexDigit (n - (n / 16) * 16)
167
169
@@ -179,12 +181,9 @@ genTokenN = \n -> if n <= 0 then yield "" else do
179
181
180genToken = genTokenN 8
182genToken = genTokenN 8
181
183
182−tooLong = \s maxLen -> not (drop maxLen s == "")
184+tooLong = \s maxLen -> drop maxLen s != ""
183
185
184−isHexChar = \c ->
186+isHexChar = \c -> contains c "0123456789abcdefABCDEF"
185− c == "0" || c == "1" || c == "2" || c == "3" || c == "4" || c == "5" || c == "6" || c == "7" || c == "8" || c == "9" ||
186− c == "a" || c == "b" || c == "c" || c == "d" || c == "e" || c == "f" ||
187− c == "A" || c == "B" || c == "C" || c == "D" || c == "E" || c == "F"
188
187
189isValidHexPairs = \s ->
188isValidHexPairs = \s ->
190 if s == "" then True {}
189 if s == "" then True {}
@@ -192,27 +191,18 @@ isValidHexPairs = \s ->
192 else if isHexChar (take 1 s) && isHexChar (take 1 (drop 1 s)) then isValidHexPairs (drop 2 s)
191 else if isHexChar (take 1 s) && isHexChar (take 1 (drop 1 s)) then isValidHexPairs (drop 2 s)
193 else False {}
192 else False {}
194
193
195−isValidHex = \s -> not (s == "") && isValidHexPairs s
194+isValidHex = \s -> s != "" && isValidHexPairs s
196
195
197normalizeHex = \s -> bytesToHex (bytesFromHex s)
196normalizeHex = \s -> bytesToHex (bytesFromHex s)
198
197
199−isDigitChar = \c -> c == "0" || c == "1" || c == "2" || c == "3" || c == "4" || c == "5" || c == "6" || c == "7" || c == "8" || c == "9"
198+isDigitChar = \c -> contains c "0123456789"
200
199
201−isAllowedServerChar = \c ->
200+isAllowedServerChar = \c -> contains c "0123456789abcdefghijklmnopqrstuvwxyz-.:"
202− isDigitChar c || c == "-" || c == "." || c == ":"
203− || c == "a" || c == "b" || c == "c" || c == "d" || c == "e" || c == "f"
204− || c == "g" || c == "h" || c == "i" || c == "j" || c == "k" || c == "l"
205− || c == "m" || c == "n" || c == "o" || c == "p" || c == "q" || c == "r"
206− || c == "s" || c == "t" || c == "u" || c == "v" || c == "w" || c == "x"
207− || c == "y" || c == "z"
208
201
209−containsBadUrlChar = \s ->
202+containsBadUrlChar = \s -> any (chars s) (\c -> not (isAllowedServerChar c))
210− if s == "" then False {}
211− else if not (isAllowedServerChar (take 1 s)) then True {}
212− else containsBadUrlChar (drop 1 s)
213
203
214isCgnatRange = \s ->
204isCgnatRange = \s ->
215− if not (take 4 s == "100.") then False {}
205+ if take 4 s != "100." then False {}
216 else if (take 1 (drop 4 s) == "7" || take 1 (drop 4 s) == "8" || take 1 (drop 4 s) == "9")
206 else if (take 1 (drop 4 s) == "7" || take 1 (drop 4 s) == "8" || take 1 (drop 4 s) == "9")
217 && isDigitChar (take 1 (drop 5 s)) && take 1 (drop 6 s) == "."
207 && isDigitChar (take 1 (drop 5 s)) && take 1 (drop 6 s) == "."
218 && isAllDigitsOrDotsUntilColonOrEnd (drop 7 s)
208 && isAllDigitsOrDotsUntilColonOrEnd (drop 7 s)
@@ -234,15 +224,9 @@ isCgnatRange = \s ->
234 then True {}
224 then True {}
235 else False {}
225 else False {}
236
226
237−containsColon = \s ->
227+containsColon = \s -> contains ":" s
238− if s == "" then False {}
239− else if take 1 s == ":" then True {}
240− else containsColon (drop 1 s)
241
228
242−containsMultipleColons = \s ->
229+containsMultipleColons = \s -> count (do c <- chars s; where c == ":"; yield c) > 1
243− if s == "" then False {}
244− else if take 1 s == ":" then containsColon (drop 1 s)
245− else containsMultipleColons (drop 1 s)
246
230
247hasOctalOctet = \s ->
231hasOctalOctet = \s ->
248 if take 1 s == "0" && isDigitChar (take 1 (drop 1 s)) && isAllDigitsOrDotsUntilColonOrEnd (drop 2 s) then True {}
232 if take 1 s == "0" && isDigitChar (take 1 (drop 1 s)) && isAllDigitsOrDotsUntilColonOrEnd (drop 2 s) then True {}
@@ -268,22 +252,6 @@ isAllDigitsOrDotsUntilColonOrEnd = \s ->
268 else if isDigitChar (take 1 s) || take 1 s == "." then isAllDigitsOrDotsUntilColonOrEnd (drop 1 s)
252 else if isDigitChar (take 1 s) || take 1 s == "." then isAllDigitsOrDotsUntilColonOrEnd (drop 1 s)
269 else False {}
253 else False {}
270
254
271−toLowerChar = \c ->
272− if c == "A" then "a" else if c == "B" then "b" else if c == "C" then "c"
273− else if c == "D" then "d" else if c == "E" then "e" else if c == "F" then "f"
274− else if c == "G" then "g" else if c == "H" then "h" else if c == "I" then "i"
275− else if c == "J" then "j" else if c == "K" then "k" else if c == "L" then "l"
276− else if c == "M" then "m" else if c == "N" then "n" else if c == "O" then "o"
277− else if c == "P" then "p" else if c == "Q" then "q" else if c == "R" then "r"
278− else if c == "S" then "s" else if c == "T" then "t" else if c == "U" then "u"
279− else if c == "V" then "v" else if c == "W" then "w" else if c == "X" then "x"
280− else if c == "Y" then "y" else if c == "Z" then "z"
281− else c
282−
283−toLower = \s ->
284− if s == "" then ""
285− else toLowerChar (take 1 s) ++ toLower (drop 1 s)
286−
287isTrailingDots = \s ->
255isTrailingDots = \s ->
288 if s == "" then True {}
256 if s == "" then True {}
289 else if take 1 s == ":" then True {}
257 else if take 1 s == ":" then True {}
@@ -379,7 +347,7 @@ extractToken = \authHeader ->
379 else ""
347 else ""
380
348
381verifySecret = \token secret ->
349verifySecret = \token secret ->
382− not (secret == "") && safeCompare token secret
350+ secret != "" && safeCompare token secret
383
351
384verifyFederationAuth = \authorization ->
352verifyFederationAuth = \authorization ->
385 verifySecret (extractToken authorization) federationSecret
353 verifySecret (extractToken authorization) federationSecret
@@ -394,7 +362,7 @@ clampTimestamp = \timestamp t -> clamp (t - messageMaxAge) t timestamp
394
362
395nextSeqTs = \t seqCounter -> if t > seqCounter then t else seqCounter + 1
363nextSeqTs = \t seqCounter -> if t > seqCounter then t else seqCounter + 1
396
364
397−isFederatedOrigin = \originServer -> not (originServer == "" || originServer == serverHostN)
365+isFederatedOrigin = \originServer -> originServer != "" && originServer != serverHostN
398
366
399takeN = \items n -> (fold (\acc e -> if acc.n >= n then acc else {n: acc.n + 1, items: union acc.items [e]}) {n: 0, items: []} items).items
367takeN = \items n -> (fold (\acc e -> if acc.n >= n then acc else {n: acc.n + 1, items: union acc.items [e]}) {n: 0, items: []} items).items
400
368
@@ -508,20 +476,20 @@ countMessagesFor = \messages toKey t ->
508validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
476validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
509validGossipEvents = \events t -> do
477validGossipEvents = \events t -> do
510 e <- events
478 e <- events
511− where isValidHex e.pubkey && not (tooLong e.pubkey maxPubkeyLen) && not (tooLong e.server maxServerNameLen) && not (isBadServerName e.server)
479+ where not (tooLong e.server maxServerNameLen) && not (isBadServerName e.server)
512 let server = normalizeServerName e.server
480 let server = normalizeServerName e.server
513− where not (server == serverHostN) && e.timestamp <= t + clockSkewTolerance
481+ where server != serverHostN && e.timestamp <= t + clockSkewTolerance
514 let ttl = clampTtl e.ttl
482 let ttl = clampTtl e.ttl
515 let expiresAt = e.timestamp + ttl * 1000
483 let expiresAt = e.timestamp + ttl * 1000
516 where expiresAt > t
484 where expiresAt > t
517− yield {eventType: e.eventType, pubkey: normalizeHex e.pubkey, server: server, expiresAt: expiresAt, timestamp: e.timestamp, ttl: ttl}
485+ yield {eventType: e.eventType, pubkey: e.pubkey, server: server, expiresAt: expiresAt, timestamp: e.timestamp, ttl: ttl}
518
486
519sendToEach : [ServerName] -> (ServerName -> (a {})) -> a {}
487sendToEach : [ServerName] -> (ServerName -> (a {})) -> a {}
520sendToEach = \servers action ->
488sendToEach = \servers action ->
521 case headOf servers of
489 case headOf servers of
522 Nothing {} -> yield {}
490 Nothing {} -> yield {}
523 Just {value: server} -> do
491 Just {value: server} -> do
524− let rest = do s <- servers; where not (s == server); yield s
492+ let rest = do s <- servers; where s != server; yield s
525 fork (action server)
493 fork (action server)
526 sendToEach rest action
494 sendToEach rest action
527
495
@@ -617,7 +585,7 @@ retryForwardsForServer = \entries remaining ->
617 else case headOf entries of
585 else case headOf entries of
618 Nothing {} -> yield {}
586 Nothing {} -> yield {}
619 Just {value: f} -> do
587 Just {value: f} -> do
620− let rest = do x <- entries; where not (x.messageId == f.messageId); yield x
588+ let rest = do x <- entries; where x.messageId != f.messageId; yield x
621 result <- fetch (federationScheme ++ f.toServer) (ForwardMessage {
589 result <- fetch (federationScheme ++ f.toServer) (ForwardMessage {
622 toKey: f.toKey, fromKey: f.fromKey, encryptedBlob: f.encryptedBlob,
590 toKey: f.toKey, fromKey: f.fromKey, encryptedBlob: f.encryptedBlob,
623 timestamp: f.timestamp, originServer: serverHostN, messageId: f.messageId,
591 timestamp: f.timestamp, originServer: serverHostN, messageId: f.messageId,
@@ -782,7 +750,7 @@ serve = \req -> case req of
782 else []
750 else []
783 yield {pr: pr, verified: True {}, peerServers: peerServers}
751 yield {pr: pr, verified: True {}, peerServers: peerServers}
784 let expiredLocal = if result.verified
752 let expiredLocal = if result.verified
785− then do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p
753+ then do p <- result.pr.expiredLocal; where p.pubkey != pubkey; yield p
786 else result.pr.expiredLocal
754 else result.pr.expiredLocal
787 fork (do
755 fork (do
788 when (result.verified && federationEnabled)
756 when (result.verified && federationEnabled)
@@ -826,7 +794,7 @@ serve = \req -> case req of
826 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
794 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
827 let toPresence = do p <- presence; where p.pubkey == to && p.expiresAt > t; yield p
795 let toPresence = do p <- presence; where p.pubkey == to && p.expiresAt > t; yield p
828 let localOnline = any toPresence (\p -> p.server == serverHostN)
796 let localOnline = any toPresence (\p -> p.server == serverHostN)
829− let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
797+ let remoteServers = do p <- toPresence; where p.server != serverHostN; yield p.server
830 forwards <- *forwards
798 forwards <- *forwards
831 let forwardServers = takeN remoteServers (maxForwardsEntries - count forwards)
799 let forwardServers = takeN remoteServers (maxForwardsEntries - count forwards)
832 let shouldForward = federationEnabled && count forwardServers > 0
800 let shouldForward = federationEnabled && count forwardServers > 0
@@ -911,13 +879,12 @@ serve = \req -> case req of
911 else acc) {server: "", best: 0} valid).server
879 else acc) {server: "", best: 0} valid).server
912 yield {pr: pr, authorized: True {}, foundServer: foundServer}
880 yield {pr: pr, authorized: True {}, foundServer: foundServer}
913 forkBroadcastOffline t result.pr
881 forkBroadcastOffline t result.pr
914− yield (respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized})
882+ yield (respond {online: result.foundServer != "", server: result.foundServer, authorized: result.authorized})
915
883
916 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
884 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
917 t <- now
885 t <- now
918 let authorized = verifyFederationAuth authorization
886 let authorized = verifyFederationAuth authorization
919 let cappedHops = clamp 0 5 hopsRemaining
887 let cappedHops = clamp 0 5 hopsRemaining
920− let fromServer = normalizeServerName fromServer
921 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
888 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
922 then do
889 then do
923 maybePruneAndBroadcast t
890 maybePruneAndBroadcast t
@@ -987,7 +954,7 @@ serve = \req -> case req of
987 when (cappedHops > 1 && federationEnabled && count effectiveEvents > 0) (
954 when (cappedHops > 1 && federationEnabled && count effectiveEvents > 0) (
988 let targets = do
955 let targets = do
989 s <- gossipResult.gossipPeerServers
956 s <- gossipResult.gossipPeerServers
990− where not (s == fromServer)
957+ where s != fromServer
991 yield s
958 yield s
992 in sendBatchedGossip targets effectiveEvents (cappedHops - 1))
959 in sendBatchedGossip targets effectiveEvents (cappedHops - 1))
993 let pr = gossipResult.pr
960 let pr = gossipResult.pr
@@ -995,11 +962,10 @@ serve = \req -> case req of
995 yield (respond {ok: True {}})
962 yield (respond {ok: True {}})
996
963
997 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
964 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
998− let originServer = normalizeServerName originServer
999 t <- now
965 t <- now
1000 let authorized = verifyFederationAuth authorization
966 let authorized = verifyFederationAuth authorization
1001 let validOrigin = isFederatedOrigin originServer
967 let validOrigin = isFederatedOrigin originServer
1002− if not (authorized && validOrigin)
968+ if not authorized || not validOrigin
1003 then do
969 then do
1004 maybePruneAndBroadcast t
970 maybePruneAndBroadcast t
1005 yield (respond {ok: False {}})
971 yield (respond {ok: False {}})
@@ -1026,11 +992,9 @@ serve = \req -> case req of
1026 yield (respond {ok: result.ok})
992 yield (respond {ok: result.ok})
1027
993
1028 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
994 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
1029− let fromServer = normalizeServerName fromServer
1030− let originServer = normalizeServerName originServer
1031 t <- now
995 t <- now
1032 let authorized = verifyFederationAuth authorization
996 let authorized = verifyFederationAuth authorization
1033− if not authorized || not (originServer == serverHostN) || fromServer == serverHostN
997+ if not authorized || originServer != serverHostN || fromServer == serverHostN
1034 then do
998 then do
1035 maybePruneAndBroadcast t
999 maybePruneAndBroadcast t
1036 yield (respond {ok: False {}})
1000 yield (respond {ok: False {}})