# How BLE Mesh Source Routing Works in BitChat: A Technical Deep Dive

> Discover how BitChat's BLE mesh source routing efficiently delivers unicast packets via pre-computed paths and automatic fallback to broadcast flooding when routes fail. Learn more!

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

---

**BitChat's BLE mesh source routing enables efficient unicast packet delivery across Bluetooth Low Energy networks by embedding a pre-computed path of relay nodes directly in each packet, with automatic fallback to broadcast flooding when routes fail.**

BitChat is an open-source, permissionless messaging system that leverages Bluetooth Low Energy (BLE) mesh networking to enable device-to-device communication without centralized servers. Starting with protocol version 2, the project introduced **source routing**—a deterministic pathfinding mechanism that dramatically reduces network congestion compared to naive flooding. This article explains how BLE mesh source routing works in BitChat based on the actual source code implementation.

## BLE Mesh Source Routing Protocol Overview

In BitChat's mesh architecture, every device can function as a relay for other peers. Before version 2, all packets were broadcast-flooded: every node retransmitted every packet it received, creating exponential traffic growth in dense networks.

Source routing changes this model entirely. When a packet includes a `RecipientID` and carries the `HAS_ROUTE` flag, the sender pre-computes an optimal path through known relays and embeds that path in the packet itself. Intermediate nodes simply read the route list to determine their next hop—no per-packet routing decisions required.

## Packet Structure for Source-Routed Messages

BitChat's protocol version 2 defines a variable-length packet format that accommodates the source route field. The exact layout is documented in [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md).

```

┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│     Header      │   Fixed Fields  │  Source Route   │     Payload     │
│    (16 bytes)   │    (16 bytes)   │  (variable)     │   (variable)    │
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Header breakdown:                                                    │
│ - Version (2)                                                       │
│ - 4-byte payload length                                             │
│ - HAS_ROUTE flag                                                    │
└─────────────────────────────────────────────────────────────────────┘

```

The **Source Route** field contains:

1. **Count** (1 byte): Number of intermediate relays
2. **Relay IDs** (Count × 8 bytes): Peer IDs of intermediate hops

**Critical invariant**: The route list contains **only** intermediate relays—never the sender or final recipient. This ensures compact routes and unambiguous parsing.

All fields including the route list are covered by an Ed25519 signature appended to the packet. Any modification to the route by a malicious relay invalidates the signature, causing the destination to drop the packet.

## Mesh Topology Discovery

Before any routing can occur, nodes must discover the network topology. BitChat implements this through **neighbor advertisements** in `IdentityAnnouncement` packets:

- Each node appends a TLV (type `0x04`) listing its direct neighbors
- These announcements are collected by `MeshTopologyTracker`
- Edges become **confirmed** only when **both** peers announce each other

This bidirectional confirmation guarantees that routing paths only traverse links with verified connectivity in both directions.

The `MeshTopologyTracker` maintains this graph in memory, providing the foundation for all path calculations when originating source-routed packets.

## Route Processing in BLEService.swift

The core routing logic executes in `handleIncomingPacket` within [[`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) (approximately lines 330-350). When a packet arrives not addressed to the local peer, the service performs this decision chain:

### Step 1: Version and Flag Validation

```swift
// Pseudocode based on source structure
if packet.version >= 2 && packet.flags.contains(.hasRoute) {
    processSourceRoutedPacket(packet)
} else {
    broadcastPacket(packet) // Legacy flood path
}

```

### Step 2: Self-Location in Route List

The service extracts the `Count` and relay IDs, then searches for the local `PeerID` in the list.

### Step 3: Next-Hop Resolution

- If found at index *i*: next hop = relay at index *i + 1*
- If local ID is the **last** entry: next hop = `RecipientID`

### Step 4: Unicast Forward

```swift
// Simplified representation of routing logic
func forwardRoutedPacket(_ packet: Packet, route: [PeerID]) {
    guard let myIndex = route.firstIndex(of: myPeerID) else {
        broadcastPacket(packet) // Defensive fallback
        return
    }
    
    let nextHop: PeerID
    if myIndex == route.count - 1 {
        nextHop = packet.recipientID
    } else {
        nextHop = route[myIndex + 1]
    }
    
    sendMessage(to: nextHop, packet: packet)
}

```

### Step 5: Flood Fallback

If `sendMessage(to:)` fails (peer unavailable, link broken), the packet falls back to `broadcastPacket`. This guarantees delivery at the cost of network efficiency—a deliberate reliability tradeoff.

## Origination Policy: When to Create Source Routes

BitChat's iOS client applies strict conditions before originating a source route. The `BLESourceRouteOriginationPolicy` enforces six requirements:

| Condition | Purpose |
|-----------|---------|
| `SenderID` equals local ID | Prevents relays from re-signing packets they didn't author |
| `RecipientID` ≠ broadcast | Source routing only applies to private (unicast) traffic |
| `TTL > 1` | Ensures sufficient hop budget for multi-hop routes |
| Recipient not directly connected | Avoids route overhead when direct link exists |
| Path ≤ 4 hops with v2-capable peers | Limits latency and guarantees decode capability |
| No recent failure in `BLESourceRouteFailureCache` | Prevents retry loops on broken paths |

If any condition fails, the packet enters the **legacy flood path**, maintaining backward compatibility with version 1 peers.

## Handling Fragmented Packets

Large payloads—file transfers, extended messages—are fragmented across multiple packets. The [[`BLEFragmentHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentHandler.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFragmentHandler.swift) ensures critical routing consistency:

- All fragments inherit the parent packet's **version 2 header**
- All fragments carry the **exact same route list**

This design prevents partial delivery: without it, individual fragments might fall back to flooding at different rates, causing reassembly failures at the destination.

## Security and Failure Handling

### Signature Protection

The Ed25519 signature covers the complete packet: header, sender/recipient IDs, source route, and payload. Relay nodes **cannot** modify routes without detection. The destination verifies this signature and drops any packet with integrity failures.

### Route Failure Caching

The [`BLESourceRouteFailureCache`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLESourceRouteFailureCache.swift) implements adaptive back-off:

1. Timeout detection: 10 seconds without acknowledgment from next hop
2. Failure recorded with timestamp
3. Route origination suppressed for 60 seconds
4. Retry permitted after back-off expires

This balances rapid failure response against premature abandonment of potentially viable paths.

## Summary

BitChat's BLE mesh source routing delivers these key capabilities:

- **Deterministic unicast paths** embedded directly in packets, eliminating per-hop routing decisions
- **Bidirectional edge confirmation** via `MeshTopologyTracker` for reliable topology knowledge
- **Graceful degradation** to broadcast flooding when routes fail or peers lack version 2 support
- **Cryptographic integrity** protecting route lists from tampering by intermediate relays
- **Adaptive failure handling** through `BLESourceRouteFailureCache` to avoid persistent broken paths

The implementation spans five primary source files, with [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) containing the critical packet processing loop that makes source routing operational.

## Frequently Asked Questions

### How does BitChat prevent malicious relays from altering source routes?

The Ed25519 signature covers the entire packet including the source route list. Any modification by an intermediate relay invalidates the signature, causing the destination to reject the packet. Relays can only forward packets intact or drop them entirely—they cannot redirect traffic without detection.

### What happens when a source-routed packet cannot reach its next hop?

BitChat implements a two-level fallback: first, the specific `sendMessage(to:)` call fails and triggers the **flood fallback** in `handleIncomingPacket`. The packet is then broadcast to all neighbors. If this still fails (or the original route repeatedly fails), the `BLESourceRouteFailureCache` records the failure and suppresses route origination to that recipient for 60 seconds.

### Why does BitChat limit source routes to 4 hops maximum?

The `BLESourceRouteOriginationPolicy` enforces a 4-hop limit for two reasons: **latency control**, as each BLE hop introduces measurable delay in a mesh network, and **version compatibility**, ensuring every relay in the path has demonstrated version 2 capability and can properly parse source-routed packets. Longer paths risk including legacy peers that would drop or mishandle the packet.