How BitChat's MessageRouter Balances Reachability and Connection State for Reliable P2P Messaging
BitChat's MessageRouter prioritizes direct delivery over active secure sessions when available, automatically falling back to encrypted courier storage and internet bridge relays when peers are only reachable or completely offline, ensuring message delivery survives intermittent BLE mesh connectivity.
In the permissionlesstech/bitchat repository, the MessageRouter serves as the central nervous system for private message delivery, intelligently routing encrypted payloads through a multi-tiered state machine. This Swift-based component distinguishes between full connection states and lightweight reachability heuristics to maximize delivery probability while minimizing latency in adversarial mesh networks.
Understanding BitChat's Four-Tier Transport State Hierarchy
The routing logic in MessageRouter.swift categorizes peer connectivity into four distinct transport-related states. Each state triggers a specific delivery strategy optimized for the current network conditions.
Connected with Secure Session (Noise Protocol)
When a peer maintains an active BLE mesh connection with a completed Noise handshake, the router identifies this via Transport.canDeliverSecurely(to:). In this state—referenced in lines [2‑13] of the sendPrivate method—the router enqueues the message, records a secure-transmission entry in secureTransmissions, and transmits directly over the transport. This path offers the lowest latency and strongest replay protection.
Connected Without Security (BLE Mesh Only)
If connectedTransport(for:) returns a valid transport but canDeliverSecurely returns false, the peer is connected to the mesh but the cryptographic handshake remains incomplete. Lines [15‑36] of the routing logic show that the router transmits the message directly while simultaneously invoking attemptCourierDeposit to distribute a sealed copy to nearby courier peers. This dual-path approach prevents message loss if the handshake never completes due to intermittent connectivity.
Reachable But Not Connected
When reachableTransport(for:) identifies a peer present in the mesh (lines [82‑84]) but no active connection exists, the router treats this as a "last-known-online" scenario. Lines [39‑48] demonstrate that the router immediately attempts transmission; however, if transport.canDeliverPromptly(to:) returns false, lines [52‑54] trigger an immediate courier deposit. This ensures messages reach peers who are technically discoverable but experiencing high latency or intermittent packet loss.
No Transport Available
If neither reachable nor connected transports exist for the recipient, lines [56‑60] show the router queuing the message without attempting direct transport delivery. Instead, it immediately invokes attemptCourierDeposit to store the encrypted envelope with available couriers or bridge relays, preparing for a future physical encounter or internet synchronization.
Transport Selection Logic in sendPrivate
The sendPrivate method (lines [92‑62]) implements a cascading priority check using two helper functions to determine the optimal delivery path:
private func reachableTransport(for peerID: PeerID) -> Transport? {
transports.first { $0.isPeerReachable(peerID) } // ⬅︎ line [82‑84]
}
private func connectedTransport(for peerID: PeerID) -> Transport? {
transports.first { $0.isPeerConnected(peerID) } // ⬅︎ line [86‑88]
}
The selection algorithm proceeds as follows:
- Connected + Secure: Checks
connectedTransport && canDeliverSecurelyfor direct delivery with session tracking. - Connected + Insecure: Falls back to
connectedTransportwith immediate courier redundancy (lines [35‑36]). - Reachable Only: Uses
reachableTransportwith conditional courier fallback if delivery cannot be prompt (lines [52‑54]). - Unreachable: Queues to outbox and triggers immediate courier deposit (lines [56‑60]).
This hierarchy ensures that connection state (full BLE link) takes precedence over reachability (mesh presence), while never abandoning messages when only weaker signals exist.
Courier Fallback Strategy for Store-and-Forward Delivery
When direct transport delivery fails or proves impossible, the router activates the courier subsystem through attemptCourierDeposit (lines [74‑98]). This method implements a multi-layered persistence strategy:
Bridge Relay and Mesh Courier Selection
First, the router requests an internet bridge deposit via requestBridgeCourierDeposit. Simultaneously, it queries eligibleCouriers (lines [140‑160]) to select up to maxCouriersPerMessage physical carriers from the mesh. The selection logic in courierDirectory.isTrustedCourier prioritizes mutual favorite peers, creating a trusted cohort of message carriers.
Continuous Retry Mechanisms
The router maintains persistence through two retry pathways. retryBridgeCourierDeposits (lines [84‑95]) periodically attempts failed internet deposits, while courierBecameAvailable (lines [106‑132]) triggers immediate re-processing when new peers connect to the BLE mesh. When a courier successfully receives the sealed envelope via courierTransport.sendCourierMessage, the router fires onMessageCarried and records the deposit for metrics tracking.
Out-of-Band Cleanup and Message TTL
To prevent unbounded outbox growth, MessageRouter implements strict resource limits. Each peer queue respects a FIFO limit defined by maxMessagesPerPeer, while messageTTLSeconds enforces a 24-hour time-to-live on all retained messages.
The cleanupExpiredMessages method (lines [785‑807]) periodically sweeps the outbox, removing stale entries. Additionally, startBridgeDepositSweep initiates a background task that calls retryBridgeCourierDeposits at configurable intervals (defaulting to 120 seconds), ensuring that expired messages are purged while viable ones receive continuous delivery attempts.
Implementation Example: Sending Private Messages
Instantiating the router requires providing concrete transport implementations and directory services:
import BitFoundation
import BitLogger
// Assume `meshTransport` and `nostrTransport` both conform to `Transport`
let router = MessageRouter(
transports: [meshTransport, nostrTransport],
now: Date.init,
courierDirectory: .favoritesBacked(),
outboxStore: MessageOutboxStore(),
metrics: StoreAndForwardMetrics()
)
// Send a private message – the router decides the best path.
router.sendPrivate(
"Hello, world!",
to: PeerID(publicKey: recipientPublicKey),
recipientNickname: "Alice",
messageID: UUID().uuidString
)
If the recipient maintains a secure Noise session, the message transmits directly; otherwise, the router automatically retains the copy and initiates courier deposits without requiring application-level logic.
Monitoring Courier Hand-offs
Track message custody changes through callback closures:
router.onMessageCarried = { messageID, peerID in
print("Message \(messageID) handed to a courier for \(peerID.id.prefix(8))")
}
router.onMessageDropped = { messageID, peerID in
print("Message \(messageID) dropped after retries for \(peerID.id.prefix(8))")
}
Reacting to New Courier Availability
When the BLE layer detects new peer connections, notify the router to retry pending deposits:
// Called by the BLE layer when a peer becomes connected.
func peerDidConnect(_ peerID: PeerID) {
router.courierBecameAvailable(peerID)
}
Configuring Bridge Retry Sweeps
Enable periodic internet bridge retry attempts:
router.startBridgeDepositSweep(interval: 120) // every 2 minutes
Summary
- BitChat's MessageRouter implements a four-tier state hierarchy distinguishing between secure connections, insecure connections, reachability, and total unavailability.
- Transport selection logic in
sendPrivateprioritizes active connections while automatically falling back to courier deposits for lower-tier states. - Courier handling via
attemptCourierDepositdistributes encrypted messages to trusted peers and internet bridges, with continuous retry mechanisms triggered bycourierBecameAvailableandretryBridgeCourierDeposits. - Resource management enforces per-peer FIFO limits and 24-hour TTLs through
cleanupExpiredMessagesto prevent outbox bloat.
Frequently Asked Questions
What is the difference between reachability and connection state in BitChat?
Reachability indicates a peer is present in the BLE mesh directory (discoverable via isPeerReachable) but may lack an active LLC link, while connection state requires an established transport channel (isPeerConnected). The MessageRouter uses reachability as a freshness heuristic for "last-known-online" scenarios, but only trusts active connections for immediate, low-latency delivery.
How does MessageRouter handle message security when no secure session exists?
When canDeliverSecurely returns false but a transport connection exists, the router transmits the message directly while simultaneously invoking attemptCourierDeposit (lines [35‑36]). This creates a sealed copy using the recipient's static Noise key from courierDirectory.noiseKey, ensuring that if the in-flight message is intercepted or the handshake fails, an encrypted backup resides with trusted couriers.
What triggers the courier fallback mechanism?
Courier fallback activates when the transport lacks security (lines [35‑36]), when reachability exists but prompt delivery fails (lines [52‑54]), or when no transport exists (lines [56‑60]). Upon any of these conditions, attemptCourierDeposit immediately selects eligible couriers and attempts bridge relay deposits.
How does BitChat prevent message outbox bloat?
The router enforces a per-peer FIFO limit via maxMessagesPerPeer and a 24-hour expiration via messageTTLSeconds. The cleanupExpiredMessages method (lines [785‑807]) runs periodic sweeps to purge stale entries, while startBridgeDepositSweep ensures active messages receive retry attempts without accumulating indefinite backlog.
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 →