offline_protocol/constants.rs
1//! Constants used throughout the offline-protocol crate.
2
3/// Default time-to-live for messages.
4pub const DEFAULT_INITIAL_TTL: u8 = 8;
5
6/// Metadata key for ACK messages indicating which message ID the ACK is for.
7///
8/// Re-exported rather than declared, because a leaf node writes the same key on
9/// the acknowledgements it owes and cannot reach this crate. The one home is
10/// `offline-protocol-sealed`; a second spelling here would be a pair that stops
11/// answering each other with nothing failing to build.
12pub use offline_protocol_sealed::ACK_FOR_KEY;
13
14/// Metadata key for ACK messages indicating the hop count.
15pub const ACK_HOP_COUNT_KEY: &str = "ack_hop_count";
16
17/// Metadata key for ACK messages indicating the transport used.
18pub const ACK_TRANSPORT_KEY: &str = "ack_transport";
19
20/// Metadata key on an ACK that answers a frame without delivering it. Absent
21/// on an ordinary delivery ACK.
22pub const ACK_STATUS_KEY: &str = "ack_status";
23
24/// [`ACK_STATUS_KEY`] value: the recipient can never decrypt the frame it
25/// names (a spent ratchet generation, a dead epoch, or the pending-decryption
26/// queue gave up on it). The sender resends the message once; it is
27/// never `message_delivered`. Still a delivery ACK in shape, so a relay holding
28/// the frame drops it.
29pub const ACK_STATUS_UNDECRYPTABLE: &str = "undecryptable";
30
31/// Metadata key on a delivery ACK that answers more than one frame: the ids
32/// after the one in [`ACK_FOR_KEY`], comma-separated. Sent only to a peer that
33/// advertised [`ACK_COALESCED_V1`](offline_protocol_sealed::ACK_COALESCED_V1),
34/// and never on an `undecryptable` answer. A peer that ignores it still
35/// settles the first id, and the rest through their resends' duplicate
36/// re-acknowledgement.
37pub const ACK_MORE_KEY: &str = "ack_more";
38
39/// How many frames one coalesced delivery ACK may answer. Five keeps a
40/// coalesced ACK inside one fragment at the 185-byte floor's neighbour, the
41/// 512-byte fragment both phones negotiate: 264 bytes plus four 37-byte ids.
42pub const ACK_COALESCE_MAX: usize = 5;
43
44/// The longest a delivery ACK waits to be coalesced with others to the same
45/// peer. Short against the 10-second acknowledgement timeout; long enough
46/// that at a few messages a second each ACK frame answers two or three.
47pub const ACK_COALESCE_WINDOW: std::time::Duration = std::time::Duration::from_millis(500);
48
49/// Metadata key marking a frame the SDK sent on its own: control traffic,
50/// key packages, data-sync frames, receipts, typing, presence, and a 1:1
51/// Welcome no user message is waiting on.
52///
53/// Set to `"1"` in the cleartext envelope so the relay can tell these from
54/// the user's own messages (both are opaque `__MLS_ENC__` ciphertext to it)
55/// and skip the "new message" push for an offline recipient. Frames built
56/// for the application's user-content sends never carry it. Unsigned like
57/// all metadata; receivers ignore it.
58pub const NO_ALERT_KEY: &str = "no_alert";
59
60/// Maximum number of entries in the outbox before evicting oldest entries.
61/// This prevents unbounded memory growth when messages cannot be delivered.
62pub const MAX_OUTBOX_ENTRIES: usize = 500;
63
64/// Absolute cap on a restored outbox entry's total lifetime, as a multiple
65/// of `outbox_max_lifetime_ms`, measured from `first_sent_at`.
66///
67/// The restore path refreshes the carrier-relative TTL of an entry that
68/// lapsed while the process was down (restart ⇒ fresh delivery window).
69/// Without an absolute bound, an app used briefly once past each lifetime
70/// keeps re-granting the entry a fresh window forever, so "expiry is
71/// terminal" would only hold in-process. Past this cap the entry is dropped
72/// at restore with a terminal `message_failed` instead of refreshed
73/// (4 × 7 days = 28 days at the default lifetime).
74pub const OUTBOX_ABSOLUTE_LIFETIME_FACTOR: i32 = 4;
75
76/// Maximum number of messages to keep in history for topology visualization.
77/// This prevents unbounded memory growth while maintaining enough history
78/// for accurate network statistics.
79pub const MAX_MESSAGE_HISTORY: usize = 1000;
80
81/// Number of messages to remove when history exceeds the maximum.
82/// This provides a buffer to avoid frequent cleanup operations.
83pub const HISTORY_CLEANUP_BATCH_SIZE: usize = 100;
84
85/// Threshold for compacting observed stats to prevent unbounded growth.
86pub const OBSERVED_STATS_COMPACT_THRESHOLD: u32 = 10_000;
87
88/// EMA (Exponential Moving Average) alpha for latency tracking.
89pub const LATENCY_EMA_ALPHA: f32 = 0.3;
90
91/// EMA alpha for hop count tracking.
92pub const HOP_COUNT_EMA_ALPHA: f32 = 0.2;
93
94/// Default chunk size for file transfers (32 KB).
95pub const DEFAULT_CHUNK_SIZE: usize = 32 * 1024;
96
97/// Chunk size for BLE file transfers (4 KB).
98/// Reduces BLE double-fragmentation: ~22 BLE fragments per chunk instead of ~240.
99pub const CHUNK_SIZE_BLE: usize = 4 * 1024;
100
101/// Chunk size for Internet file transfers (256 KB).
102/// Fewer round-trips over WebSocket where bandwidth is plentiful.
103pub const CHUNK_SIZE_INTERNET: usize = 256 * 1024;
104
105/// Maximum chunks in flight for BLE media transfers.
106/// BLE's low bandwidth means fewer concurrent chunks to avoid congestion.
107pub const MEDIA_WINDOW_SIZE_BLE: usize = 2;
108
109/// Maximum chunks in flight for Internet media transfers.
110pub const MEDIA_WINDOW_SIZE_INTERNET: usize = 8;
111
112/// Default maximum chunks in flight for media transfers.
113pub const DEFAULT_MEDIA_WINDOW_SIZE: usize = 4;
114
115/// Maximum entries in the dedicated media outbox.
116/// Smaller than the main outbox since the sliding window limits in-flight chunks.
117pub const MAX_MEDIA_OUTBOX_ENTRIES: usize = 100;
118
119/// Maximum persisted outbound media transfer descriptors restored after a
120/// restart (newest-first). Small on purpose: each maps to a
121/// `MediaResendRequired` the app is expected to act on, and concurrent
122/// transfers are already capped per peer.
123pub const MAX_MEDIA_DESCRIPTORS: usize = 16;
124
125/// Maximum concurrent outbound media transfers per recipient when chunks are
126/// MLS-encrypted.
127///
128/// Encrypted chunks share the recipient's 1:1 session ratchet with text, and
129/// the receiver retains out-of-order message keys for only
130/// `SENDER_RATCHET_OUT_OF_ORDER_TOLERANCE` (32) generations. Each transfer
131/// keeps up to `MEDIA_WINDOW_SIZE_INTERNET` (8) chunks in flight, so two
132/// concurrent transfers bound the media-side in-flight gap at 16
133/// generations, leaving room for up to 16 further messages interleaved on
134/// the same ratchet before a delayed chunk's key is deleted. That residual
135/// is not a guarantee: text sends are unbounded, and a chunk delayed while
136/// 32+ interleaved messages decrypt first is permanently undecryptable (the
137/// receiver surfaces this as `MessageDecryptionFailed`). Without this cap,
138/// concurrent transfers alone could push a delayed chunk beyond the
139/// tolerance and permanently stall its transfer.
140pub const MAX_CONCURRENT_MEDIA_TRANSFERS_PER_PEER: usize = 2;
141
142/// The receiver's forward window has to sit well above the number of
143/// messages this engine keeps in flight to one peer.
144///
145/// Every resend is re-sealed under a fresh ratchet generation, and a receiver
146/// that drops a resend as a duplicate of a message it already holds skips
147/// that generation. A congested link can therefore open a gap of several
148/// in-flight ceilings, and a receiver more than the window behind decrypts
149/// nothing from that sender until the pair re-keys. The 2026-10-19 volume
150/// test hit the old window of 1000 at exactly the in-flight ceiling of 1000.
151/// Stated to the compiler so raising the ceiling past it fails the build.
152const _: () = assert!(
153 offline_protocol_mls::group::SENDER_RATCHET_MAXIMUM_FORWARD_DISTANCE as usize
154 >= 4 * offline_protocol_reliability::constants::DEFAULT_MAX_PENDING_ACKS
155);
156
157/// How many of a peer's transfer slots the SDK's own document-layer traffic
158/// may occupy at once.
159///
160/// Strictly below [`MAX_CONCURRENT_MEDIA_TRANSFERS_PER_PEER`], and that is
161/// the whole point. A document-layer transfer is invisible to the
162/// application on both sides, so an application whose own `send_media` fails
163/// with [`crate::Error::MediaTransferLimit`] cannot see what is holding the
164/// slots or wait for it to finish. Leaving one slot always reachable keeps
165/// that error explainable by what the application itself is doing.
166pub const MAX_CONCURRENT_INTERNAL_MEDIA_TRANSFERS_PER_PEER: usize = 1;
167
168/// Metadata key indicating preferred transport for a message.
169pub const TRANSPORT_PREFERENCE_KEY: &str = "transport_preference";
170
171/// Transport preference value: prefer internet transport.
172pub const TRANSPORT_PREFERENCE_INTERNET: &str = "internet";
173
174/// Metadata key carrying the original content type on file-chunk messages.
175pub const ORIGINAL_CONTENT_TYPE_KEY: &str = "original_content_type";
176
177/// Maximum number of times a message may be forwarded. Prevents unbounded
178/// forwarding chains from malicious or buggy clients.
179pub const MAX_FORWARD_COUNT: u32 = 100;
180
181/// Maximum messages to send in a single flush operation.
182/// Prevents blocking the process loop when many messages are pending.
183pub const FLUSH_BATCH_LIMIT: usize = 20;