How BitChat Implements BLE Mesh Routing, TTL, and Multi-Hop Communication

BitChat uses a deterministic source-routing protocol where senders embed the complete hop path into each BLE packet, enforcing a maximum of 4 intermediate hops with a TTL counter that decrements at each relay to prevent infinite loops and bound network latency.

BitChat is an open-source decentralized messaging application that routes messages over Bluetooth Low Energy (BLE) mesh networks. The permissionlesstech/bitchat repository implements a custom source-routing architecture to solve the challenges of multi-hop communication in resource-constrained mobile environments. This design places routing intelligence at the sender, allowing intermediate nodes to simply forward packets based on embedded path instructions while TTL mechanisms prevent infinite forwarding cycles.

Core Routing Architecture

The BLE mesh routing stack consists of three coordinated components that handle path selection, failure recovery, and packet forwarding.

BLESourceRouteOriginationPolicy

Located in bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift, this component determines whether messages travel directly, via single hop, or through multi-hop source routes. It enforces the maximum hop limit by consulting TransportConfig.bleSourceRouteMaxIntermediateHops and initializes the TTL value based on path length.

BLESourceRouteFailureCache

Implemented in bitchat/Services/BLE/BLESourceRouteFailureCache.swift, this cache monitors route health and suppresses failing paths. When a hop fails, it records the failure and avoids retrying the same route for TransportConfig.bleSourceRouteSuppressionSeconds (default 60 seconds), forcing route recalculation.

BLEService

Found in bitchat/Services/BLE/BLEService.swift, this service executes the actual packet encoding and forwarding. It maintains a per-session sourceRouteFailures cache and handles the per-hop TTL decrement and link ID extraction.

Source-Routing Mechanics

BitChat employs source routing rather than distributed routing tables, making paths deterministic and debugging predictable.

Route Construction

When originating a message, the BLESourceRouteOriginationPolicy.route() function evaluates the destination peer ID against known BLE links. For direct neighbors, it omits the route list. For distant peers, it constructs a hop list containing up to 4 intermediate BLEIngressLinkID values representing the chain of devices between sender and recipient.

Packet Structure and TTL Encoding

Each BitchatPacket contains an 8-bit ttl field and an isSourceRouted boolean flag (the RSR bit). During packet signing, both TTL and RSR bits are temporarily cleared from the signature payload per BLEAnnounceHandlingPolicy to ensure cryptographic integrity, then re-inserted before transmission.

The binary layout follows this structure:


| Header | TTL | RSR Flag | Route Hops (link IDs) | Encrypted Payload |

The TTL value initializes to the hop count plus one (route.hops.count + 1), ensuring sufficient lifetime to reach the destination while bounding propagation.

Multi-Hop Forwarding Logic

Intermediate nodes process source-routed packets through a standardized forwarding pipeline that enforces TTL constraints.

Per-Hop Processing

When BLEService.handleReceivedPacket receives a packet, it performs the following validation:

  1. TTL Check: If packet.ttl == 0, the packet drops immediately to prevent infinite loops.
  2. Route Extraction: The service pops the next BLEIngressLinkID from the sourceRoute array.
  3. TTL Decrement: The service decrements packet.ttl by one.
  4. Forward: The packet transmits to the next link via BLE write operations.

This process repeats until TTL reaches zero at the final destination, at which point MessageRouter delivers the payload to the application layer.

Deterministic Path Enforcement

Because the sender embeds the complete path, relays do not perform route lookups. This design minimizes processing overhead on battery-constrained mobile devices and eliminates route discovery broadcast storms common in traditional mesh protocols.

Route Failure Handling

The mesh implements proactive failure detection to avoid black-holed packets.

Suppression Windows

When a hop fails (timeout or link loss), BLESourceRouteFailureCache records the incident. The cache suppresses the failed route for TransportConfig.bleSourceRouteSuppressionSeconds (60 seconds), causing subsequent messages to trigger new route construction or fall back to flood-fill broadcast mode. Successful deliveries refresh a confirmation window of TransportConfig.bleSourceRouteConfirmationWindowSeconds (10 seconds).

Fallback Strategies

If source routing fails due to topology changes, senders can revert to BLE broadcast flooding, trading efficiency for delivery guarantees until the policy constructs a new valid source route.

Implementation Examples

The following Swift snippets demonstrate the core routing workflow.

Route origination and packet preparation:

// Determine optimal path
let route = BLESourceRouteOriginationPolicy.route(
    destination: destPeerID,
    maxHops: TransportConfig.bleSourceRouteMaxIntermediateHops
)

// Construct packet with TTL
var packet = BitchatPacket(type: .message)
packet.ttl = UInt8(route.hops.count + 1)
packet.isSourceRouted = true
packet.sourceRoute = route.hops
packet.sign(using: mySigningKey)

Intermediate node forwarding logic inside BLEService:

func handleReceivedPacket(_ packet: BitchatPacket) {
    // TTL enforcement prevents infinite loops
    guard packet.ttl > 0 else { return }
    
    // Extract next hop and decrement TTL
    let nextLink = packet.sourceRoute.removeFirst()
    packet.ttl -= 1
    
    // Forward to next BLE link
    send(to: nextLink, packet: packet)
}

Failure cache interaction:

// Inside BLESourceRouteFailureCache
if let failure = cache.recordFailure(for: route, at: now) {
    if failure.isSuppressed {
        // Trigger route reconstruction
        rebuildRoute()
    }
}

Configuration Constants

The routing behavior is governed by compile-time configuration values:

  • TransportConfig.bleSourceRouteMaxIntermediateHops: Caps routes at 4 intermediate hops to maintain latency under 30-60ms per hop on iOS devices.
  • TransportConfig.bleSourceRouteSuppressionSeconds: 60-second window during which failed routes are avoided.
  • TransportConfig.bleSourceRouteConfirmationWindowSeconds: 10-second window that confirms route viability after successful delivery.

Summary

  • Source routing embeds complete hop paths in packet headers, eliminating distributed routing tables on intermediate nodes.
  • TTL enforcement bounds packet lifetime to the hop count plus one, preventing infinite loops in cyclic mesh topologies.
  • Four-hop maximum keeps end-to-end latency predictable and manageable on mobile BLE stacks.
  • Failure suppression caches route failures for 60 seconds, automatically triggering path recalculation without manual intervention.
  • Deterministic forwarding allows intermediate devices to operate as simple relays, reducing CPU and battery consumption.

Frequently Asked Questions

How does BitChat prevent infinite packet loops in BLE mesh networks?

BitChat prevents infinite loops through a TTL (time-to-live) counter embedded in every BitchatPacket. The sender initializes TTL to the number of intermediate hops plus one. Each relay node decrements the counter before forwarding, and any node receiving a packet with TTL equal to zero drops it immediately. This hard limit ensures packets expire after traversing the maximum configured path length.

Why does BitChat use source routing instead of traditional mesh routing tables?

Source routing places path calculation entirely on the sender, allowing intermediate nodes to forward packets by simply reading the next hop from a pre-encoded list rather than consulting distributed routing tables. This design reduces memory footprint and CPU usage on battery-constrained mobile devices, eliminates route convergence delays, and makes network behavior deterministic and easier to debug.

What happens when an intermediate node in a source route goes offline?

When a node fails to forward a packet, BLESourceRouteFailureCache records the failure and suppresses that specific route for 60 seconds. The sender detects the failure and either reconstructs a new source route using available links or falls back to broadcast flooding. This suppression mechanism prevents wasteful retries over broken paths while encouraging rapid adaptation to topology changes.

How is the maximum hop count of 4 determined for BitChat mesh networks?

The 4-hop limit in TransportConfig.bleSourceRouteMaxIntermediateHops represents a trade-off between network reach and latency on iOS and Android BLE stacks. Each hop introduces approximately 30-60ms of processing and connection latency; limiting paths to 4 hops keeps end-to-end delivery under 250ms while maintaining reliable packet delivery across typical mobile device densities.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →