on master help sign in
Remove message TTL feature and simplify message expiration to always use 7-day default
commite5cfebc9ec98c657402808ce5f234fa892b92c0c authoriko <mail@iko.soy> dateSat 25 Apr 2026, 17:28 (6 months ago) parent66a992c
1 file changed+34−69
server.knot+34−69
server.knot+34−69
@@ -39,10 +39,6 @@ maxPresenceTtl : Int<S>
3939maxPresenceTtl = 3600 -- max presence TTL from gossip
4040maxPubkeyLen = 256
4141maxBlobLen = 65536
42−-- Minimum message TTL (sender-specified). Prevents very short TTLs that would expire
43−-- before the recipient polls, wasting mailbox slots and federation bandwidth.
44−minMessageTtl : Int<Ms>
45−minMessageTtl = 60000 -- 1 minute
4642maxSignatureLen = 256
4743maxIpLen = 45 -- max length of an IP address string (IPv6 = 45 chars); used in matchIp instead of maxServerNameLen
4844maxMessageIds = 100
@@ -339,7 +335,6 @@ type Challenge = {pubkey: PubkeyHex, challenge: Text, expiresAt: Timestamp, ip:
339335-- Authenticated session; token is a random hex string
340336type Session = {token: Text, pubkey: PubkeyHex, expiresAt: Timestamp, ip: Text, createdAt: Timestamp}
341337-- Stored message; originServer is "" for locally-originated messages, set to sender server for federated forwards.
342−-- expiresAt: computed from sender-specified TTL or default unackedMessageMaxAge.
343338-- Messages with expiresAt <= now are pruned and excluded from polls.
344339-- senderKey: pubkey of the authenticated sender (locally-originated only; "" for federation forwards).
345340-- Used by /messages/cancel to verify the caller is the actual sender.
@@ -347,9 +342,7 @@ type Message = {messageId: MessageIdHex, toKey: PubkeyHex, encryptedBlob: BlobHe
347342-- Presence: tracks that a pubkey is online at a given server, with expiry
348343type PresenceEntry = {pubkey: PubkeyHex, server: AnyServerName, expiresAt: Timestamp, timestamp: Timestamp}
349344-- Queued federation forward: message waiting to be delivered to a remote server.
350−-- messageTtl: remaining TTL in ms at time of enqueue (0 = default). Passed to the
351−-- receiving server so it can compute its own expiresAt.
352−type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: Text, retries: Int, messageTtl: Int}
345+type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: Text, retries: Int}
353346-- Tracks message IDs that were acked by a polling client, pending federation ack back to origin
354347type AckedEntry = {messageId: MessageIdHex, toKey: PubkeyHex, ackedAt: Timestamp, originServer: ServerName, retries: Int}
355348-- Terminal state: message delivered and ack confirmed (used for dedup after ack overflow)
@@ -387,7 +380,7 @@ type UniqueKeyIpEntry = {ip: Text, keys: [PubkeyHex], windowStart: Timestamp}
387380type ForwardNonce = {messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, seenAt: Timestamp}
388381
389382-- Batch send types
390−type BatchMessage = {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, messageId: OptionalMessageId, messageTtl: Int}
383+type BatchMessage = {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, messageId: OptionalMessageId}
391384type BatchResult = {messageId: OptionalMessageId, status: MessageStatus}
392385
393386-- Sender expiration notification: tracks when a locally-originated message expired unread.
@@ -800,10 +793,7 @@ route ClientApi where
800793 = Poll
801794 /messages
802795 -- Send an E2E encrypted message to a recipient pubkey.
803− -- messageTtl: optional TTL in milliseconds (0 = default unackedMessageMaxAge).
804− -- Clamped to [minMessageTtl, unackedMessageMaxAge]. Short TTLs for ephemeral data
805− -- (typing indicators, read receipts) free mailbox slots faster than the default 7 days.
806− POST {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, messageId: OptionalMessageId, messageTtl: Int} / headers {authorization: Text, clientIp: Text}
796+ POST {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, messageId: OptionalMessageId} / headers {authorization: Text, clientIp: Text}
807797 -> {status: MessageStatus, messageId: OptionalMessageId, retryAfterMs: Int, error: Text}
808798 = SendMessage
809799 -- Check delivery status of a sent message (caller must know recipient to prove ownership)
@@ -853,14 +843,12 @@ route FederationApi where
853843 -> {ok: Bool, error: Text}
854844 = RecvGossip
855845 -- Receive a forwarded message from a peer server on behalf of a sender.
856− -- messageTtl: remaining TTL in ms (0 = default). Allows short-lived messages to
857− -- expire on the destination server without waiting for the full unackedMessageMaxAge.
858846 -- NOTE: senderKey is intentionally NOT included in the forward protocol. The sender's
859847 -- identity is inside the E2E encrypted blob — exposing it in plaintext would break the
860848 -- privacy model (the server never learns who sent a message). /messages/cancel for
861849 -- federated messages requires the client to include sender identity in the encrypted
862850 -- payload; the server cannot verify sender identity for federation-forwarded messages.
863− POST {toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: ServerName, messageId: MessageIdHex, fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, messageTtl: Int} /forward headers {authorization: Text}
851+ POST {toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: ServerName, messageId: MessageIdHex, fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text} /forward headers {authorization: Text}
864852 -> {ok: Bool, error: Text}
865853 = ForwardMessage
866854 -- Acknowledge delivery of messages back to the origin server
@@ -2499,13 +2487,6 @@ fetchFederation = \server request -> do
24992487clampTtl : Int<S> -> Int<S>
25002488clampTtl = clamp 1 maxPresenceTtl
25012489
2502−-- Compute message expiresAt from a sender-specified TTL (in ms).
2503−-- 0 = default (unackedMessageMaxAge). Non-zero values are clamped to [minMessageTtl, unackedMessageMaxAge].
2504−computeMessageExpiresAt : Timestamp -> Int -> Timestamp
2505−computeMessageExpiresAt = \receivedAt messageTtl ->
2506− if messageTtl <= 0 then receivedAt + unackedMessageMaxAge
2507− else receivedAt + clamp minMessageTtl unackedMessageMaxAge messageTtl
2508−
25092490clampTimestamp : Timestamp -> Timestamp -> Timestamp
25102491clampTimestamp = \timestamp t -> clamp (t - messageMaxAge) t timestamp
25112492
@@ -3370,7 +3351,7 @@ retryForwardsForServer = \entries remaining ->
33703351 toKey: f.toKey, encryptedBlob: f.encryptedBlob,
33713352 timestamp: f.timestamp, originServer: f.originServer, messageId: f.messageId,
33723353 fromServer: serverHostN, requestTimestamp: t,
3373− authorization: auth, correlationId: corrId, messageTtl: f.messageTtl})
3354+ authorization: auth, correlationId: corrId})
33743355 case result of
33753356 Err e -> do
33763357 logWarn ("federation: forward to " ++ f.toServer ++ " failed for " ++ f.messageId ++ ": " ++ show e)
@@ -3961,10 +3942,9 @@ forwardCandidates = \targets messages forwards t ->
39613942 where m.toKey == target.pubkey
39623943 where none serverFwdIds (\fid -> fid == m.messageId)
39633944 let origin = if m.originServer == "" then serverHostN else m.originServer
3964− let remainingTtl = if m.expiresAt > 0 && m.expiresAt > t then max 0 (m.expiresAt - t) else 0
39653945 yield {messageId: m.messageId, toServer: target.server, createdAt: t,
39663946 toKey: m.toKey, encryptedBlob: m.encryptedBlob, timestamp: m.timestamp,
3967− originServer: origin, retries: 0, messageTtl: remainingTtl})) [] targets
3947+ originServer: origin, retries: 0})) [] targets
39683948
39693949
39703950challengeMatch = \challenges pubkey challenge ip t ->
@@ -4278,9 +4258,8 @@ handlePoll = \cursor generation watchKeys authorization clientIp respond -> do
42784258-- Core send logic: dedup, rate limiting, store message, create forwards.
42794259-- Called inside an atomic block by both handleSendMessage and sendBatchInner.
42804260-- Caller handles auth, drain checks, and messageId generation.
4281−-- messageTtl: sender-specified TTL in ms (0 = default unackedMessageMaxAge).
4282−sendMessageCore = \senderKey to encryptedBlob clampedTs effectiveMsgId messageTtl t ->
4283− if senderKey == to then yield {status: MsRejected {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0}
4261+sendMessageCore = \senderKey to encryptedBlob clampedTs effectiveMsgId t ->
4262+ if senderKey == to then yield {status: MsRejected {}, remoteServers: [], retryAfterMs: 0}
42844263 else do
42854264 let msgBucket = pubkeyBucket to
42864265 bucket <- readMessageBucket msgBucket
@@ -4291,37 +4270,37 @@ sendMessageCore = \senderKey to encryptedBlob clampedTs effectiveMsgId messageTt
42914270 -- expire but before the nonce expires.
42924271 nonceBkt <- readNonceBucket msgBucket
42934272 let isDup = checkMessageDup bucket effectiveMsgId to || hasMessage effectiveMsgId to ackedBkt || hasMessage effectiveMsgId to delivBkt || any nonceBkt (\n -> n.messageId == effectiveMsgId && n.toKey == to)
4294− if isDup then yield {status: MsDuplicate {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0}
4273+ if isDup then yield {status: MsDuplicate {}, remoteServers: [], retryAfterMs: 0}
42954274 else do
42964275 sendRates <- *sendRates
42974276 sendRate <- effectiveSendRate
42984277 let rateResult = checkPubkeyRate sendRates senderKey sendRate sendRateWindowMs maxSendRateEntries t
4299− if rateResult.limited then yield {status: MsRateLimited {}, remoteServers: [], retryAfterMs: rateResult.retryAfterMs, msgExpiresAt: 0}
4278+ if rateResult.limited then yield {status: MsRateLimited {}, remoteServers: [], retryAfterMs: rateResult.retryAfterMs}
43004279 else do
43014280 set *sendRates = rateResult.entries
43024281 pairRates <- *pairRates
43034282 let pairResult = checkPairRate pairRates senderKey to maxPairRatePerPair pairRateWindowMs maxPairRateEntries t
43044283 set *pairRates = pairResult.entries
4305− if pairResult.limited then yield {status: MsRateLimited {}, remoteServers: [], retryAfterMs: pairResult.retryAfterMs, msgExpiresAt: 0}
4284+ if pairResult.limited then yield {status: MsRateLimited {}, remoteServers: [], retryAfterMs: pairResult.retryAfterMs}
43064285 else do
43074286 recvRates <- *recvRates
43084287 recvRate <- effectiveRecvRate
43094288 let recvResult = checkPubkeyRate recvRates to recvRate recvRateWindowMs maxRecvRateEntries t
4310− if recvResult.limited then yield {status: MsRateLimited {}, remoteServers: [], retryAfterMs: recvResult.retryAfterMs, msgExpiresAt: 0}
4289+ if recvResult.limited then yield {status: MsRateLimited {}, remoteServers: [], retryAfterMs: recvResult.retryAfterMs}
43114290 else do
43124291 set *recvRates = recvResult.entries
43134292 totalMsgCount <- *totalMessageCount
4314− if totalMsgCount >= maxMessages then yield {status: MsRejected {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0}
4293+ if totalMsgCount >= maxMessages then yield {status: MsRejected {}, remoteServers: [], retryAfterMs: 0}
43154294 else do
43164295 recipientCounts <- readRcBucket msgBucket
43174296 let recipientCount = getRecipientCount to recipientCounts
4318− if recipientCount >= maxMessagesPerRecipient then yield {status: MsRejected {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0}
4297+ if recipientCount >= maxMessagesPerRecipient then yield {status: MsRejected {}, remoteServers: [], retryAfterMs: 0}
43194298 else do
43204299 recipientPresence <- readPresenceBucket (pubkeyBucket to)
43214300 seqCounter <- *seqCounter
43224301 let seqTs = nextSeqTs t seqCounter
43234302 set *seqCounter = seqTs
4324− let msgExpiresAt = computeMessageExpiresAt seqTs messageTtl
4303+ let msgExpiresAt = seqTs + unackedMessageMaxAge
43254304 writeMessageBucket msgBucket (union bucket
43264305 [{messageId: effectiveMsgId, toKey: to,
43274306 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs, expiresAt: msgExpiresAt, senderKey: senderKey}])
@@ -4345,8 +4324,7 @@ sendMessageCore = \senderKey to encryptedBlob clampedTs effectiveMsgId messageTt
43454324 let forwardsEnqueued = if federationEnabled && count remoteServers > 0 then do
43464325 let fbi = pubkeyBucket to
43474326 fwdBkt <- readFwdBucket fbi
4348− let remainingTtl = if messageTtl > 0 then max 0 (msgExpiresAt - t) else 0
4349− let newFwds = do s <- remoteServers; where none fwdBkt (\f -> f.messageId == effectiveMsgId && f.toServer == s); yield {messageId: effectiveMsgId, toServer: s, createdAt: t, toKey: to, encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: serverHostN, retries: 0, messageTtl: remainingTtl}
4327+ let newFwds = do s <- remoteServers; where none fwdBkt (\f -> f.messageId == effectiveMsgId && f.toServer == s); yield {messageId: effectiveMsgId, toServer: s, createdAt: t, toKey: to, encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: serverHostN, retries: 0}
43504328 totalFwdCount <- countAllForwards 0
43514329 let globalCap = maxForwardsEntries - totalFwdCount
43524330 -- Per-key cap: prevents one key from consuming disproportionate forward slots
@@ -4360,12 +4338,10 @@ sendMessageCore = \senderKey to encryptedBlob clampedTs effectiveMsgId messageTt
43604338 let status = if localOnline then MsDelivered {}
43614339 else if federationEnabled && forwardsEnqueued then MsFederated {}
43624340 else MsQueued {}
4363− yield {status: status, remoteServers: remoteServers, retryAfterMs: 0, msgExpiresAt: msgExpiresAt}
4341+ yield {status: status, remoteServers: remoteServers, retryAfterMs: 0}
43644342
43654343-- Fork federation forwards for messages that need to be delivered to remote servers.
4366−-- msgExpiresAt: the stored message's expiration timestamp (used to compute remaining TTL
4367−-- at forward time, ensuring the remote server gets the correct remaining lifetime).
4368−forkSendForwards = \effectiveMsgId to encryptedBlob clampedTs msgExpiresAt remoteServers ->
4344+forkSendForwards = \effectiveMsgId to encryptedBlob clampedTs remoteServers ->
43694345 when (count remoteServers > 0 && federationEnabled) (
43704346 fork (sendToEach remoteServers (\server -> do
43714347 stillPending <- atomic do
@@ -4375,15 +4351,11 @@ forkSendForwards = \effectiveMsgId to encryptedBlob clampedTs msgExpiresAt remot
43754351 fwdT <- now
43764352 fwdAuth <- generateFederationAuth "forward" server
43774353 fwdCorrId <- genTokenN 1
4378− -- Compute remaining TTL from the stored message's expiration (not the original TTL).
4379− -- This ensures the remote server gets the correct remaining lifetime, even if
4380− -- the fork is delayed by network issues or server load.
4381− let fwdTtl = if msgExpiresAt > 0 then max 0 (msgExpiresAt - fwdT) else 0
43824354 fwdResult <- fetchFederation server (ForwardMessage {
43834355 toKey: to, encryptedBlob: encryptedBlob,
43844356 timestamp: clampedTs, originServer: serverHostN, messageId: effectiveMsgId,
43854357 fromServer: serverHostN, requestTimestamp: fwdT,
4386− authorization: fwdAuth, correlationId: fwdCorrId, messageTtl: fwdTtl})
4358+ authorization: fwdAuth, correlationId: fwdCorrId})
43874359 case fwdResult of
43884360 Err e -> do
43894361 logWarn ("federation: forward to " ++ server ++ " failed for " ++ effectiveMsgId ++ ": " ++ show e)
@@ -4409,13 +4381,11 @@ sendBatchForwardsSequential = \msgs server ->
44094381 fwdT <- now
44104382 fwdAuth <- generateFederationAuth "forward" server
44114383 fwdCorrId <- genTokenN 1
4412− -- Use stored msgExpiresAt to compute remaining TTL (not original messageTtl)
4413− let fwdTtl = if r.msgExpiresAt > 0 then max 0 (r.msgExpiresAt - fwdT) else 0
44144384 fwdResult <- fetchFederation server (ForwardMessage {
44154385 toKey: r.toKey, encryptedBlob: r.blob,
44164386 timestamp: r.ts, originServer: serverHostN, messageId: r.messageId,
44174387 fromServer: serverHostN, requestTimestamp: fwdT,
4418− authorization: fwdAuth, correlationId: fwdCorrId, messageTtl: fwdTtl})
4388+ authorization: fwdAuth, correlationId: fwdCorrId})
44194389 case fwdResult of
44204390 Err e -> do
44214391 logWarn ("federation: forward to " ++ server ++ " failed for " ++ r.messageId ++ ": " ++ show e)
@@ -4431,7 +4401,7 @@ sendBatchForwardsSequential = \msgs server ->
44314401-- POST /messages: Send an encrypted message to a recipient.
44324402-- Deduplicates by messageId across messages, ackedIds, and deliveredIds.
44334403-- Rate-limited per sender key. If recipient is known on a remote server, forwards via federation.
4434−handleSendMessage = \to encryptedBlob timestamp messageId messageTtl authorization clientIp respond -> do
4404+handleSendMessage = \to encryptedBlob timestamp messageId authorization clientIp respond -> do
44354405 t <- now
44364406 globalRateLimited <- checkGlobalRate t
44374407 if globalRateLimited then yield (respond {status: MsRateLimited {}, messageId: "", retryAfterMs: 0, error: "rate_limited"})
@@ -4447,15 +4417,15 @@ handleSendMessage = \to encryptedBlob timestamp messageId messageTtl authorizati
44474417 let clampedTs = clampTimestamp timestamp t
44484418 result <- atomic do
44494419 dp <- *drainPhase
4450− if dp >= 2 then yield {status: MsDraining {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0}
4420+ if dp >= 2 then yield {status: MsDraining {}, remoteServers: [], retryAfterMs: 0}
44514421 else withSessionAuth authorization t clientIp
4452− (yield {status: MsUnauthorized {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0})
4453− (\senderKey -> sendMessageCore senderKey to encryptedBlob clampedTs effectiveMsgId messageTtl t)
4422+ (yield {status: MsUnauthorized {}, remoteServers: [], retryAfterMs: 0})
4423+ (\senderKey -> sendMessageCore senderKey to encryptedBlob clampedTs effectiveMsgId t)
44544424 case result.status of
44554425 MsRateLimited {} -> logWarn ("sendMessage: rate limited for " ++ effectiveMsgId)
44564426 MsRejected {} -> when (result.retryAfterMs == 0) (logWarn ("sendMessage: rejected for " ++ effectiveMsgId ++ " (capacity or mailbox full)"))
44574427 _ -> yield {}
4458− forkSendForwards effectiveMsgId to encryptedBlob clampedTs result.msgExpiresAt result.remoteServers
4428+ forkSendForwards effectiveMsgId to encryptedBlob clampedTs result.remoteServers
44594429 yield (case result.status of
44604430 MsDuplicate {} -> respond {status: MsDuplicate {}, messageId: effectiveMsgId, retryAfterMs: 0, error: ""}
44614431 MsDelivered {} -> respond {status: MsDelivered {}, messageId: effectiveMsgId, retryAfterMs: 0, error: ""}
@@ -4806,7 +4776,7 @@ handleRecvGossip = \events hopsRemaining fromServer requestTimestamp correlation
48064776-- (fromServer != originServer) where an intermediate server re-forwards because
48074777-- the recipient moved. Cascade is bounded by dedup (same messageId won't be stored
48084778-- twice) and forwardCandidates (won't re-forward to the same server).
4809−handleForwardMessage = \toKey encryptedBlob timestamp originServer messageId fromServer requestTimestamp correlationId messageTtl authorization respond -> do
4779+handleForwardMessage = \toKey encryptedBlob timestamp originServer messageId fromServer requestTimestamp correlationId authorization respond -> do
48104780 t <- now
48114781 let corrId = sanitizeCorrelationId correlationId
48124782 when (corrId != "") (logInfo ("[CORR] " ++ corrId ++ " forward " ++ messageId ++ " from " ++ fromServer))
@@ -4890,12 +4860,7 @@ handleForwardMessage = \toKey encryptedBlob timestamp originServer messageId fro
48904860 seqCounter <- *seqCounter
48914861 let seqTs = nextSeqTs t seqCounter
48924862 set *seqCounter = seqTs
4893− -- Federation forwards: use raw TTL without minMessageTtl clamp.
4894− -- The TTL represents remaining lifetime from the origin server, which
4895− -- may legitimately be less than minMessageTtl near expiry. Clamping
4896− -- to minMessageTtl would extend the message's life on each hop.
4897− let fwdExpiresAt = if messageTtl <= 0 then seqTs + unackedMessageMaxAge
4898− else seqTs + clamp 1 unackedMessageMaxAge messageTtl
4863+ let fwdExpiresAt = seqTs + unackedMessageMaxAge
48994864 writeMessageBucket bkt (union bucket
49004865 [{messageId: messageId, toKey: toKey,
49014866 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs, expiresAt: fwdExpiresAt, senderKey: ""}])
@@ -5443,7 +5408,7 @@ handleRestore = \version presenceData ackedIdsData ackOverflowData deliveredIdsD
54435408 if not authorized then yield (respond {ok: False {}, error: "unauthorized", presenceCount: 0, ackedIdsCount: 0, deliveredIdsCount: 0})
54445409 else do
54455410 -- Version 0 = pre-versioning snapshot, version 1 = pre-forwardNonces, version 2 = pre-blockedPeers,
5446− -- version 3 = pre-messageTtl in ForwardEntry, version 4 = pre-allowedPeers
5411+ -- version 3 = pre-ForwardEntry changes, version 4 = pre-allowedPeers
54475412 -- (accept all for backwards compatibility; missing fields default to empty list or 0)
54485413 if version != 0 && version != 1 && version != 2 && version != 3 && version != 4 && version != snapshotVersion then do
54495414 logError ("restore: incompatible snapshot version " ++ show version ++ " (expected " ++ show snapshotVersion ++ ")")
@@ -5781,18 +5746,18 @@ sendBatchInner = \senderKey messages t acc ->
57815746 when (msg.timestamp < t - messageMaxAge / 2 && t - msg.timestamp <= messageMaxAge)
57825747 (logWarn ("sendBatch: stale timestamp (" ++ show ((t - msg.timestamp) / 86400000) ++ " days old) for " ++ effectiveMsgId))
57835748 result <- if t - msg.timestamp > messageMaxAge
5784− then yield {messageId: effectiveMsgId, status: MsRejected {}, remoteServers: [], toKey: "", blob: "", ts: 0, msgExpiresAt: 0, retryAfterMs: 0}
5749+ then yield {messageId: effectiveMsgId, status: MsRejected {}, remoteServers: [], toKey: "", blob: "", ts: 0, retryAfterMs: 0}
57855750 else do
57865751 let clampedTs = clampTimestamp msg.timestamp t
57875752 sendResult <- atomic do
57885753 dp <- *drainPhase
5789− if dp >= 2 then yield {status: MsDraining {}, remoteServers: [], retryAfterMs: 0, msgExpiresAt: 0}
5790− else sendMessageCore senderKey msg.to msg.encryptedBlob clampedTs effectiveMsgId msg.messageTtl t
5754+ if dp >= 2 then yield {status: MsDraining {}, remoteServers: [], retryAfterMs: 0}
5755+ else sendMessageCore senderKey msg.to msg.encryptedBlob clampedTs effectiveMsgId t
57915756 case sendResult.status of
57925757 MsRateLimited {} -> logWarn ("sendBatch: rate limited for " ++ effectiveMsgId)
57935758 MsRejected {} -> logWarn ("sendBatch: rejected for " ++ effectiveMsgId ++ " (capacity or mailbox full)")
57945759 _ -> yield {}
5795− yield {messageId: effectiveMsgId, status: sendResult.status, remoteServers: sendResult.remoteServers, toKey: msg.to, blob: msg.encryptedBlob, ts: clampedTs, msgExpiresAt: sendResult.msgExpiresAt, retryAfterMs: sendResult.retryAfterMs}
5760+ yield {messageId: effectiveMsgId, status: sendResult.status, remoteServers: sendResult.remoteServers, toKey: msg.to, blob: msg.encryptedBlob, ts: clampedTs, retryAfterMs: sendResult.retryAfterMs}
57965761 sendBatchInner senderKey split.rest t (union acc [result])
57975762
57985763-- POST /maintenance/block: Add a server to the federation blocklist.
@@ -6146,7 +6111,7 @@ serveInner = \req -> case req of
61466111 RevokeSession {targetToken, authorization, clientIp, respond} -> handleRevoke targetToken authorization clientIp respond
61476112 Logout {authorization, clientIp, respond} -> handleLogout authorization clientIp respond
61486113 Poll {cursor, generation, watchKeys, authorization, clientIp, respond} -> handlePoll cursor generation watchKeys authorization clientIp respond
6149− SendMessage {to, encryptedBlob, timestamp, messageId, messageTtl, authorization, clientIp, respond} -> handleSendMessage to encryptedBlob timestamp messageId messageTtl authorization clientIp respond
6114+ SendMessage {to, encryptedBlob, timestamp, messageId, authorization, clientIp, respond} -> handleSendMessage to encryptedBlob timestamp messageId authorization clientIp respond
61506115 MessageStatus {to, messageId, authorization, clientIp, respond} -> handleMessageStatus to messageId authorization clientIp respond
61516116 AckMessages {messageIds, authorization, clientIp, respond} -> handleAckMessages messageIds authorization clientIp respond
61526117 SendBatch {messages, authorization, clientIp, respond} -> handleSendBatch messages authorization clientIp respond
@@ -6156,7 +6121,7 @@ serveInner = \req -> case req of
61566121 LookupPresence {pubkey, authorization, clientIp, respond} -> handleLookupPresence pubkey authorization clientIp respond
61576122 BatchLookupPresence {pubkeys, authorization, clientIp, respond} -> handleBatchLookupPresence pubkeys authorization clientIp respond
61586123 RecvGossip {events, hopsRemaining, fromServer, requestTimestamp, correlationId, authorization, respond} -> handleRecvGossip events hopsRemaining fromServer requestTimestamp correlationId authorization respond
6159− ForwardMessage {toKey, encryptedBlob, timestamp, originServer, messageId, fromServer, requestTimestamp, correlationId, messageTtl, authorization, respond} -> handleForwardMessage toKey encryptedBlob timestamp originServer messageId fromServer requestTimestamp correlationId messageTtl authorization respond
6124+ ForwardMessage {toKey, encryptedBlob, timestamp, originServer, messageId, fromServer, requestTimestamp, correlationId, authorization, respond} -> handleForwardMessage toKey encryptedBlob timestamp originServer messageId fromServer requestTimestamp correlationId authorization respond
61606125 FederationAck {messageIds, originServer, fromServer, requestTimestamp, correlationId, authorization, respond} -> handleFederationAck messageIds originServer fromServer requestTimestamp correlationId authorization respond
61616126 FederationCancel {messageId, toKey, fromServer, requestTimestamp, correlationId, authorization, respond} -> handleFederationCancel messageId toKey fromServer requestTimestamp correlationId authorization respond
61626127 VerifyToken {token, scope, clientIp, respond} -> handleVerifyToken token scope clientIp respond