@@ -26,6 +26,7 @@ maxServerNameLen = 128
26maxMessageIdLen = 128
26maxMessageIdLen = 128
27maxTokenLen = 256
27maxTokenLen = 256
28maxChallenges = 10000
28maxChallenges = 10000
30-- ============================================
31-- ============================================
@@ -189,12 +190,12 @@ containsMultipleColons = \s ->
189 else containsMultipleColons (drop 1 s)
190 else containsMultipleColons (drop 1 s)
191hasOctalOctet = \s ->
192hasOctalOctet = \s ->
192− if take 1 s == "0" && isDigitChar (take 1 (drop 1 s)) then True {}
193+ if take 1 s == "0" && isDigitChar (take 1 (drop 1 s)) && isAllDigitsOrDotsUntilColonOrEnd (drop 2 s) then True {}
193 else hasOctalOctetAfterDot s
194 else hasOctalOctetAfterDot s
195hasOctalOctetAfterDot = \s ->
196hasOctalOctetAfterDot = \s ->
196 if s == "" then False {}
197 if s == "" then False {}
197− else if take 1 s == "." && take 1 (drop 1 s) == "0" && isDigitChar (take 1 (drop 2 s)) then True {}
198+ else if take 1 s == "." && take 1 (drop 1 s) == "0" && isDigitChar (take 1 (drop 2 s)) && isAllDigitsOrDotsUntilColonOrEnd (drop 3 s) then True {}
198 else hasOctalOctetAfterDot (drop 1 s)
199 else hasOctalOctetAfterDot (drop 1 s)
200hasHexPrefix = \s ->
201hasHexPrefix = \s ->
@@ -212,6 +213,12 @@ isAllDigitsUntilColonOrEnd = \s ->
212 else if isDigitChar (take 1 s) then isAllDigitsUntilColonOrEnd (drop 1 s)
213 else if isDigitChar (take 1 s) then isAllDigitsUntilColonOrEnd (drop 1 s)
213 else False {}
214 else False {}
216+isAllDigitsOrDotsUntilColonOrEnd = \s ->
217+ if s == "" then True {}
218+ else if take 1 s == ":" then True {}
219+ else if isDigitChar (take 1 s) || take 1 s == "." then isAllDigitsOrDotsUntilColonOrEnd (drop 1 s)
215toLowerChar = \c ->
222toLowerChar = \c ->
216 if c == "A" then "a" else if c == "B" then "b" else if c == "C" then "c"
223 if c == "A" then "a" else if c == "B" then "b" else if c == "C" then "c"
217 else if c == "D" then "d" else if c == "E" then "e" else if c == "F" then "f"
224 else if c == "D" then "d" else if c == "E" then "e" else if c == "F" then "f"
@@ -228,14 +235,21 @@ toLower = \s ->
228 if s == "" then ""
235 if s == "" then ""
229 else toLowerChar (take 1 s) ++ toLower (drop 1 s)
236 else toLowerChar (take 1 s) ++ toLower (drop 1 s)
238+isTrailingDots = \s ->
239+ if s == "" then True {}
240+ else if take 1 s == ":" then True {}
241+ else if take 1 s == "." then isTrailingDots (drop 1 s)
231stripTrailingDot = \s ->
244stripTrailingDot = \s ->
232 if s == "" then ""
245 if s == "" then ""
233− else if take 1 s == "." && drop 1 s == "" then ""
246+ else if take 1 s == "." && isTrailingDots (drop 1 s) then stripTrailingDot (drop 1 s)
234− else if take 1 s == "." && take 1 (drop 1 s) == ":" then drop 1 s
235 else take 1 s ++ stripTrailingDot (drop 1 s)
247 else take 1 s ++ stripTrailingDot (drop 1 s)
237isReservedHighRange = \s ->
249isReservedHighRange = \s ->
250+ (take 2 s == "22" && (take 1 (drop 2 s) == "4" || take 1 (drop 2 s) == "5" || take 1 (drop 2 s) == "6" || take 1 (drop 2 s) == "7" || take 1 (drop 2 s) == "8" || take 1 (drop 2 s) == "9") && take 1 (drop 3 s) == ".")
251+ || (take 2 s == "23" && isDigitChar (take 1 (drop 2 s)) && take 1 (drop 3 s) == ".")
238− (take 2 s == "24" && isDigitChar (take 1 (drop 2 s)) && take 1 (drop 3 s) == ".")
252+ || (take 2 s == "24" && isDigitChar (take 1 (drop 2 s)) && take 1 (drop 3 s) == ".")
239 || (take 3 s == "250" && take 1 (drop 3 s) == ".")
253 || (take 3 s == "250" && take 1 (drop 3 s) == ".")
240 || (take 3 s == "251" && take 1 (drop 3 s) == ".")
254 || (take 3 s == "251" && take 1 (drop 3 s) == ".")
241 || (take 3 s == "252" && take 1 (drop 3 s) == ".")
255 || (take 3 s == "252" && take 1 (drop 3 s) == ".")
@@ -256,7 +270,9 @@ isBadServerName = \s ->
256 || take 7 sn == "172.24." || take 7 sn == "172.25." || take 7 sn == "172.26." || take 7 sn == "172.27."
270 || take 7 sn == "172.24." || take 7 sn == "172.25." || take 7 sn == "172.26." || take 7 sn == "172.27."
257 || take 7 sn == "172.28." || take 7 sn == "172.29." || take 7 sn == "172.30." || take 7 sn == "172.31."
271 || take 7 sn == "172.28." || take 7 sn == "172.29." || take 7 sn == "172.30." || take 7 sn == "172.31."
258 || take 7 sn == "198.18." || take 7 sn == "198.19."
272 || take 7 sn == "198.18." || take 7 sn == "198.19."
273+ || take 8 sn == "192.0.0."
259 || take 8 sn == "192.0.2."
274 || take 8 sn == "192.0.2."
275+ || take 10 sn == "192.88.99."
260 || take 12 sn == "198.51.100."
276 || take 12 sn == "198.51.100."
261 || take 11 sn == "203.0.113."
277 || take 11 sn == "203.0.113."
262 || isReservedHighRange sn
278 || isReservedHighRange sn
@@ -265,7 +281,7 @@ isBadServerName = \s ->
265 || containsMultipleColons sn
281 || containsMultipleColons sn
266 || hasOctalOctet sn
282 || hasOctalOctet sn
267 || hasHexPrefix sn
283 || hasHexPrefix sn
268− || (isDigitChar (take 1 sn) && isAllDigitsUntilColonOrEnd sn)
284+ || (isDigitChar (take 1 sn) && isAllDigitsOrDotsUntilColonOrEnd sn)
270lookupSession = \sessions token t ->
286lookupSession = \sessions token t ->
271 single (do
287 single (do
@@ -300,7 +316,7 @@ safeCompareLoop = \s1 s2 result ->
300 then result + (if s1 == "" then 0 else 1)
316 then result + (if s1 == "" then 0 else 1)
301 else safeCompareLoop (drop 1 s1) (drop 1 s2) (result + (if take 1 s1 == take 1 s2 then 0 else 1))
317 else safeCompareLoop (drop 1 s1) (drop 1 s2) (result + (if take 1 s1 == take 1 s2 then 0 else 1))
303−safeCompare = \a b -> safeCompareLoop a b 0 == 0
319+safeCompare = \a b -> not (a == "") && not (b == "") && safeCompareLoop a b 0 == 0
305sendToEach = \servers action ->
321sendToEach = \servers action ->
306 if count servers == 0
322 if count servers == 0
@@ -312,14 +328,11 @@ sendToEach = \servers action ->
312 then yield {}
328 then yield {}
314 catch (action server) (\err -> do
330 catch (action server) (\err -> do
315− println ("federation: error reaching " ++ server ++ ", retrying (1/2): " ++ show err)
331+ println ("federation: error reaching " ++ server ++ ", retrying: " ++ show err)
316− sleep 1000
332+ sleep 500
317 catch (action server) (\err2 -> do
333 catch (action server) (\err2 -> do
318− println ("federation: retry 1 failed for " ++ server ++ ", retrying (2/2): " ++ show err2)
334+ println ("federation: retry failed for " ++ server ++ ": " ++ show err2)
320− catch (action server) (\err3 -> do
321− println ("federation: all retries exhausted for " ++ server ++ ": " ++ show err3)
322− yield {})))
335+ yield {}))
323 sendToEach rest action
336 sendToEach rest action
325pruneExpired = \t -> do
338pruneExpired = \t -> do
@@ -382,14 +395,14 @@ sendBatchedGossip = \peerServers allEvents ->
382 sendBatchedGossip peerServers rest
395 sendBatchedGossip peerServers rest
384broadcastOfflineGossip = \t peerServers expiredLocal -> do
397broadcastOfflineGossip = \t peerServers expiredLocal -> do
385− let expiredPubkeys = fold (\acc p ->
398+ let expiredEntries = fold (\acc p ->
386− if count (do a <- acc; where a == p.pubkey; yield a) > 0 then acc
399+ if count (do a <- acc; where a.pubkey == p.pubkey; yield a) > 0 then acc
387− else union acc [p.pubkey]) [] expiredLocal
400+ else union acc [{pubkey: p.pubkey, ttl: clampTtl ((p.expiresAt - p.timestamp) / 1000)}]) [] expiredLocal
388− if not (federationSecret == "") && count expiredPubkeys > 0 && count peerServers > 0
401+ if not (federationSecret == "") && count expiredEntries > 0 && count peerServers > 0
390 let gossipEvents = do
403 let gossipEvents = do
391− pk <- expiredPubkeys
404+ e <- expiredEntries
392− yield {eventType: "offline", pubkey: pk, server: serverHost, timestamp: t, ttl: 3600}
405+ yield {eventType: "offline", pubkey: e.pubkey, server: serverHost, timestamp: t, ttl: e.ttl}
393 sendBatchedGossip peerServers gossipEvents
406 sendBatchedGossip peerServers gossipEvents
394 else yield {}
407 else yield {}
@@ -476,8 +489,17 @@ serve = \req -> case req of
476 let sessionsCapped = if count existingForKey >= 10 && oldestExpiry >= 0
489 let sessionsCapped = if count existingForKey >= 10 && oldestExpiry >= 0
477 then cappedFold.sessions
490 then cappedFold.sessions
478 else sessions
491 else sessions
492+ let globalOldestSessionExpiry = if count sessionsCapped >= maxSessions
493+ then fold (\acc s -> if acc == -1 || s.expiresAt < acc then s.expiresAt else acc) -1 sessionsCapped
495+ let globalSessionsCapped = if globalOldestSessionExpiry >= 0
496+ then (fold (\acc s ->
497+ if not acc.removed && s.expiresAt == globalOldestSessionExpiry
498+ then {removed: True {}, items: acc.items}
499+ else {removed: acc.removed, items: union acc.items [s]}) {removed: False {}, items: []} sessionsCapped).items
479 set *sessions = if sigValid
501 set *sessions = if sigValid
480− then union sessionsCapped [{token: token, pubkey: pubkey, expiresAt: expiry}]
502+ then union globalSessionsCapped [{token: token, pubkey: pubkey, expiresAt: expiry}]
481 else sessions
503 else sessions
482 let presenceWithout = do
504 let presenceWithout = do
483 p <- presence
505 p <- presence
@@ -656,19 +678,10 @@ serve = \req -> case req of
656 set *messages = case session of
678 set *messages = case session of
657 Nothing {} -> messages
679 Nothing {} -> messages
658 Just {value: _} -> diff messages ackedMessages
680 Just {value: _} -> diff messages ackedMessages
659− presence <- *presence
660− let knownPeers = fold (\acc p ->
661− if p.server == serverHost then acc
662− else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
663− else union acc [p.server]) [] presence
664− let knownMsgOrigins = fold (\acc m ->
665− if m.originServer == "" || m.originServer == serverHost then acc
666− else if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
667− else union acc [m.originServer]) [] messages
668 let ok = case session of
681 let ok = case session of
669 Nothing {} -> False {}
682 Nothing {} -> False {}
670 _ -> True {}
683 _ -> True {}
671− yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, knownPeers: knownPeers, knownMsgOrigins: knownMsgOrigins}
684+ yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
672 let fedMessages = do
685 let fedMessages = do
673 m <- result.ackedMessages
686 m <- result.ackedMessages
674 where not (m.originServer == "")
687 where not (m.originServer == "")
@@ -676,8 +689,6 @@ serve = \req -> case req of
677 let originServers = fold (\acc m ->
690 let originServers = fold (\acc m ->
678 if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
691 if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
679− else if count (do k <- result.knownPeers; where k == m.originServer; yield k) == 0
680− && count (do k <- result.knownMsgOrigins; where k == m.originServer; yield k) == 0 then acc
681 else union acc [m.originServer]) [] fedMessages
692 else union acc [m.originServer]) [] fedMessages
682 respond {ok: result.ok}
693 respond {ok: result.ok}
683 if result.ok && count originServers > 0 && not (federationSecret == "")
694 if result.ok && count originServers > 0 && not (federationSecret == "")
@@ -760,7 +771,7 @@ serve = \req -> case req of
760 o <- offlineEntries
771 o <- offlineEntries
761 where o.pubkey == e.pubkey
772 where o.pubkey == e.pubkey
762 where o.server == e.server
773 where o.server == e.server
763− where o.timestamp >= e.timestamp
774+ where o.timestamp > e.timestamp
764 yield o) == 0
775 yield o) == 0
765 yield {pubkey: e.pubkey, server: e.server, expiresAt: e.expiresAt, timestamp: e.timestamp}
776 yield {pubkey: e.pubkey, server: e.server, expiresAt: e.expiresAt, timestamp: e.timestamp}
766 let affectedKeys = union
777 let affectedKeys = union
@@ -934,13 +945,13 @@ serve = \req -> case req of
934 o <- offlineEntries
945 o <- offlineEntries
935 where o.pubkey == normalizeHex e.pubkey
946 where o.pubkey == normalizeHex e.pubkey
936 where o.server == e.server
947 where o.server == e.server
937− where o.timestamp >= e.timestamp
948+ where o.timestamp > e.timestamp
938 yield o) > 0)
949 yield o) > 0)
940 let effectiveEvents = do
951 let effectiveEvents = do
941 e <- validEvents
952 e <- validEvents
942 where count (do a <- gossipResult.appliedKeys; where a.pubkey == normalizeHex e.pubkey; where a.server == e.server; yield a) > 0
953 where count (do a <- gossipResult.appliedKeys; where a.pubkey == normalizeHex e.pubkey; where a.server == e.server; yield a) > 0
943− yield e
954+ yield {eventType: e.eventType, pubkey: normalizeHex e.pubkey, server: e.server, timestamp: e.timestamp, ttl: e.ttl}
944 respond {ok: validRequest}
955 respond {ok: validRequest}
945 if not gossipResult.withinCapacity
956 if not gossipResult.withinCapacity
946 then println ("gossip: presence at capacity (" ++ show maxPresenceEntries ++ "), limiting new entries from " ++ fromServer)
957 then println ("gossip: presence at capacity (" ++ show maxPresenceEntries ++ "), limiting new entries from " ++ fromServer)