BitChat Message Freshness Policies and TTL Capping: A Complete Guide
BitChat controls message propagation and storage lifetimes through hop-based TTL limits and age-based expiry windows defined in TransportConfig.swift, capping mesh packet travel at 7 hops in sparse networks and 5 in dense topologies while expiring stored public messages after 6 hours.
BitChat, developed by the permissionlesstech/bitchat repository, implements a strict freshness model to prevent mesh network flooding and bound storage growth. The BitChat message freshness policies combine time-to-live (TTL) counters for packet routing with age-based eviction for persisted data, all centralized in the TransportConfig type for auditability. These mechanisms ensure that messages either reach their destination within a limited hop budget or are discarded to maintain network efficiency.
Core Freshness Policies Defined in TransportConfig
All freshness limits reside in bitchat/Services/TransportConfig.swift, providing a single source of truth for mesh behavior and storage management.
Mesh Packet TTL Defaults
The default hop limit for regular mesh packets is 7 hops, defined as TransportConfig.messageTTLDefault at lines 8–9. When a packet originates, its TTL header is set to this value; each intermediate hop decrements the counter until it reaches zero and the packet is dropped.
Fragment Relay TTL Caps
Media fragmentation uses topology-aware TTL capping to prevent broadcast storms:
- Sparse graphs:
bleFragmentRelayTtlCapis set to 7 (line 29), matching the regular packet default to ensure fragments travel as far as text messages. - Dense graphs:
bleFragmentRelayTtlCapDenseis capped at 5 (line 30), reducing propagation depth to prevent overwhelming highly connected networks.
Storage and Sync Age Limits
Local storage enforces strict age limits to bound database size:
- Public message sync:
syncPublicMessageMaxAgeSecondslimits broadcasts to 6 hours (line 74). - General sync messages:
syncMaxMessageAgeSecondscaps walk-synced messages at 15 minutes (line 69). - Private media receipts:
privateMediaReceivedLedgerTTLSecondsretains receipts for 7 days (lines 21–24) before eviction. - Pre-key bundles:
syncPrekeyBundleMaxAgeSecondsvalidates store-and-forward encryption keys for 24 hours (line 104). - Location drops:
locationDropExpirySecondsexpires geo-notes after 24 hours (line 26). - Deduplication cache:
messageDedupMaxAgeSecondssuppresses duplicate IDs for 5 minutes (line 32).
How TTL Capping Works in RelayController
The RelayController.swift file enforces TTL constraints when forwarding packets. At line 26, the controller calculates the effective TTL for the next hop:
let ttlCap = min(ttl, TransportConfig.messageTTLDefault)
Sparse topology handling applies bleFragmentRelayTtlCap (7), allowing fragments to traverse the full mesh depth. Dense topology handling applies bleFragmentRelayTtlCapDense (5), cutting propagation short to prevent fragment storms while maintaining local delivery.
The hop count for any received packet is recoverable via originTTL – receivedTTL, implemented in localPackages/BitFoundation/Sources/BitFoundation/MeshPingPayload.swift.
Storage Freshness and Message Eviction
When the background sync task processes public timelines, it discards any entry older than syncPublicMessageMaxAgeSeconds (6 hours). This prevents stale broadcasts from persisting indefinitely. Private-media receipts follow a longer 7-day retention window via privateMediaReceivedLedgerTTLSeconds, after which they are purged to reclaim storage while maintaining enough history for reliable delivery confirmation.
Practical Implementation Examples
Creating a Mesh Packet with TTL
To originate a message with the correct freshness metadata:
import BitFoundation
let payload = try MeshPingPayload(
nonce: Data([0x01, 0x02]),
originTTL: TransportConfig.messageTTLDefault
)
let packet = BitchatPacket(
type: MessageType.ping.rawValue,
ttl: TransportConfig.messageTTLDefault,
payload: payload.toData()
)
Validating Message Age Before Display
To check if a stored message remains fresh according to the public sync policy:
func shouldDisplay(_ messageTimestamp: UInt64) -> Bool {
let age = Date().timeIntervalSince1970 - Double(messageTimestamp) / 1000.0
return age < TransportConfig.syncPublicMessageMaxAgeSeconds
}
Summary
- Hop limits: Default mesh TTL is 7 hops; dense networks reduce fragment relay to 5 hops.
- Storage caps: Public messages expire after 6 hours; private media receipts last 7 days.
- Configuration: All policies are centralized in
TransportConfig.swiftfor easy auditing. - Enforcement:
RelayController.swiftcaps TTL at line 26;MeshPingPayload.swifttracks hop counts.
Frequently Asked Questions
What is the default TTL for BitChat mesh packets?
The default TTL is 7 hops, defined as TransportConfig.messageTTLDefault in bitchat/Services/TransportConfig.swift at lines 8–9. This value represents the maximum number of intermediary devices a packet can traverse before being automatically discarded by the mesh layer.
How does BitChat prevent fragment flooding in dense networks?
BitChat uses topology-aware capping via TransportConfig.bleFragmentRelayTtlCapDense, which sets a hard limit of 5 hops for fragment-relay packets in dense graphs (line 30 of TransportConfig.swift). The RelayController.swift enforces this cap at line 26 by taking the minimum of the current TTL and the configured cap, preventing media storms while preserving local delivery.
How long does BitChat retain private media receipts?
Private media receipts are retained for 7 days (604,800 seconds) as specified by TransportConfig.privateMediaReceivedLedgerTTLSeconds at lines 21–24 of TransportConfig.swift. After this period, the background eviction task purges these entries to prevent unbounded storage growth while maintaining sufficient history for delivery confirmation.
Where is the message freshness policy configured in BitChat source code?
All freshness policies are centralized in bitchat/Services/TransportConfig.swift, which defines constants for TTL defaults, fragment caps, sync age limits, and storage eviction windows. This single-file configuration allows operators to audit and tune the entire freshness model without modifying routing logic in RelayController.swift or storage logic elsewhere.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →