# How BitChat's MessageRouter Balances Reachability and Connection State for Reliable P2P Messaging

> Discover how BitChat's MessageRouter ensures reliable P2P messaging by balancing reachability and connection state. Learn how it handles offline peers and intermittent connectivity for guaranteed delivery.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-08

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/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:

```swift
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:

1. **Connected + Secure**: Checks `connectedTransport && canDeliverSecurely` for direct delivery with session tracking.
2. **Connected + Insecure**: Falls back to `connectedTransport` with immediate courier redundancy (lines [35‑36]).
3. **Reachable Only**: Uses `reachableTransport` with conditional courier fallback if delivery cannot be prompt (lines [52‑54]).
4. **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:

```swift
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:

```swift
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:

```swift
// 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:

```swift
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 `sendPrivate` prioritizes active connections while automatically falling back to courier deposits for lower-tier states.
- **Courier handling** via `attemptCourierDeposit` distributes encrypted messages to trusted peers and internet bridges, with continuous retry mechanisms triggered by `courierBecameAvailable` and `retryBridgeCourierDeposits`.
- **Resource management** enforces per-peer FIFO limits and 24-hour TTLs through `cleanupExpiredMessages` to 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.