# How Source-Based Routing Works in BitChat's BLE Mesh

> Discover how BitChat's BLE mesh uses source-based routing with explicit relay paths for efficient unicast forwarding and fallback flooding to ensure reliable communication.

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

---

**BitChat's BLE mesh implements source-based routing in protocol version 2 by embedding explicit relay paths into packet headers, allowing unicast forwarding through intermediate nodes while falling back to broadcast flooding when routes fail.**

BitChat is a permissionless Bluetooth Low Energy (BLE) mesh network where every device acts as a potential relay for peers. Starting with protocol version 2, the network introduces **source-based routing**—a mechanism that replaces the default broadcast-flooding approach with efficient unicast paths for directed traffic. This article examines the packet structure, routing logic, and failure handling implemented in the `permissionlesstech/bitchat` repository.

## Packet Structure and the HAS_ROUTE Flag

Source-based routing in BitChat begins with the version 2 packet format defined in [`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md). The protocol introduces a `HAS_ROUTE` flag in the 16-byte header that signals the presence of a source-route field.

### Version 2 Header Layout

The packet structure follows this exact byte layout:

- **Header** – 16 bytes (Version 2 identifier, 4-byte payload length, `HAS_ROUTE` flag)
- **Fixed Fields** – `SenderID` (8 bytes), `RecipientID` (8 bytes)
- **Source Route** – Variable length: 1-byte `Count` followed by `Count × 8` bytes of intermediate peer IDs
- **Payload** – Length indicated by the 4-byte length field
- **Signature** – Ed25519 signature covering the entire packet (header, IDs, route, and payload)

The route list **excludes** the sender and final recipient, containing only the intermediate relay IDs that form the path.

## Topology Discovery and Confirmed Edges

Before routing can occur, nodes must discover the mesh topology. Each device advertises its **direct neighbors** via a TLV (type `0x04`) appended to the `IdentityAnnouncement` packet.

These neighbor lists feed into the `MeshTopologyTracker` class, which constructs a mesh graph where edges are marked **confirmed** only when both peers announce each other. This bidirectional verification ensures that routes only traverse links capable of two-way communication, stored in [`bitchat/MeshTopologyTracker.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/MeshTopologyTracker.swift).

## The Routing Decision Engine in BLEService.swift

When a packet arrives that is not addressed to the local peer, the [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) file (specifically around line 330-350 in the `handleIncomingPacket` method) executes the routing logic:

### Parsing Incoming Routes

1. **Version and Flag Check** – If `Version ≥ 2` and `HAS_ROUTE` is set, the service treats the packet as source-routed.
2. **Self-Location** – The service extracts the route list and searches for the local `PeerID` within the array.

### Determining the Next Hop

The next hop selection follows precise indexing rules:

- If the local ID is found at index *i*, the next hop is the peer ID at position *i + 1*.
- If the local ID is the last entry in the route list, the next hop becomes the final `RecipientID`.

The code uses `meshTopology` to resolve these identifiers before calling `BLEService.sendMessage(to:)` to unicast the packet.

### Fallback to Broadcast Flooding

If the next hop cannot be reached (e.g., the link no longer exists), the system falls back to `BLEService.broadcastPacket`, ensuring delivery via the legacy flood mechanism while preserving backward compatibility.

## Route Origination Policy (iOS Implementation)

The iOS client does not attach routes to every packet. The `BLESourceRouteOriginationPolicy` enforces six strict conditions before originating a source route:

1. **Local Authorship** – `SenderID` must equal the local device ID (relays never re-sign packets).
2. **Directed Traffic** – `RecipientID` must not be a broadcast address.
3. **Adequate TTL** – TTL must be greater than 1, ensuring sufficient hop budget.
4. **Non-Direct Recipient** – The recipient must not be directly connected.
5. **Path Constraints** – The confirmed-edge path must be ≤ 4 hops, and every intermediate hop must have observed version 2 traffic.
6. **No Recent Failures** – The `BLESourceRouteFailureCache` must not contain a recent failure entry for this recipient.

If any condition fails, the packet transmits via the broadcast flood path, maintaining compatibility with version 1 peers.

## Handling Large Payloads: Fragmentation

Large file transfers require packet fragmentation. The [`BLEFragmentHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentHandler.swift) implementation ensures that **all fragments inherit the parent packet's version 2 header and the exact same route list**.

This inheritance guarantees that every fragment travels the identical unicast path, preventing individual fragments from falling back to flooding while others follow the route, which could cause reassembly failures or security issues.

## Security and Failure Recovery

### Signature Coverage

The route list is protected by the same Ed25519 signature that covers the entire packet. Any modification to the route—such as a malicious relay inserting or removing hops—invalidates the signature, causing the destination to drop the packet.

### Failure Caching and Backoff

When a routed packet fails to receive acknowledgment from the next hop within 10 seconds, `BLESourceRouteFailureCache` records the incident. Subsequent routing attempts to that recipient are suppressed for 60 seconds, after which the system retries the route. This backoff mechanism prevents persistent attempts over broken links while allowing eventual recovery when topology changes.

## Summary

- **BitChat's BLE mesh** uses protocol version 2 to enable source-based routing via a `HAS_ROUTE` flag and explicit relay lists in packet headers.
- **Topology tracking** in [`MeshTopologyTracker.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshTopologyTracker.swift) maintains a confirmed-edge graph from bidirectional neighbor advertisements.
- **Routing decisions** in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) parse the route list to determine the next hop, falling back to broadcast flooding when the next hop is unreachable.
- **Origination policy** requires six conditions including path length ≤ 4 hops and no recent failures, enforced by `BLESourceRouteOriginationPolicy`.
- **Fragmentation** preserves routing consistency by copying the parent route list to all fragments via [`BLEFragmentHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentHandler.swift).
- **Security** relies on Ed25519 signatures covering the entire packet including the route, with failure recovery managed by `BLESourceRouteFailureCache` using 10-second timeouts and 60-second backoffs.

## Frequently Asked Questions

### How does BitChat prevent routing loops with source-based routing?

BitChat prevents routing loops by design: the source route is an **explicit ordered list** of intermediate nodes. Each relay searches for its own ID in the list and forwards only to the specific next hop at index *i + 1*. Since packets never deviate from this predetermined path and relays do not modify the route list (which would invalidate the Ed25519 signature), loops cannot form within the specified route structure.

### What happens if an intermediate node in a source route becomes unavailable?

If [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) cannot reach the calculated next hop (stored in `meshTopology`), the system immediately falls back to `BLEService.broadcastPacket`. This broadcast flooding guarantees message delivery across the mesh at the cost of increased bandwidth, ensuring reliability even when topology changes invalidate precomputed routes.

### Can legacy version 1 nodes participate in source-routed packets?

Legacy version 1 nodes cannot parse source-routed packets because they lack support for the `HAS_ROUTE` flag and variable-length route field. However, BitChat maintains backward compatibility: if any node in the potential path has not observed version 2 traffic, or if the origination policy detects version 1 peers in the route, the sender transmits via the legacy broadcast flood path instead of attaching a source route.

### How does BitChat handle routing failures and broken paths?

When a routed packet fails to receive acknowledgment within 10 seconds, `BLESourceRouteFailureCache` marks the recipient as unreachable for 60 seconds. During this suppression window, the iOS client avoids originating new routes to that destination, defaulting instead to broadcast transmission. After the backoff period expires, the system clears the cache entry and attempts to establish a fresh route through the updated mesh topology.