# How BitChat Achieves Multi-Hop Mesh Networking with TTL-Based Relay Decisions

> Discover how BitChat achieves multi-hop mesh networking using TTL-based relay decisions. Learn about its autonomous packet routing and efficient network communication.

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

---

**BitChat implements multi-hop mesh networking by attaching a TTL (time-to-live) field to every packet, decrementing it at each hop and making autonomous relay decisions based on remaining TTL, network density, and message type.**

BitChat is an open-source Swift application that builds resilient peer-to-peer communication over Bluetooth Low Energy (BLE). Understanding how BitChat achieves multi-hop mesh networking with TTL-based relay decisions reveals a sophisticated decentralized approach that requires no central coordinator while preventing infinite packet circulation.

## The TTL Field: Foundation of Hop-Limited Propagation

Every packet in BitChat carries an **originTTL** value that establishes its maximum propagation distance. This design enables nodes to calculate how far a packet has traveled and determine whether to continue forwarding it.

### Packet Structure and Hop Counting

The `MeshPingPayload` struct in `BitFoundation` defines the TTL-bearing payload structure:

```swift
// localPackages/BitFoundation/Sources/BitFoundation/MeshPingPayload.swift
public struct MeshPingPayload {
    public let originTTL: UInt8               // TTL the packet was launched with
    // ...
    /// Number of links a packet crossed, derived from TTL decrements
    public static func hopCount(originTTL: UInt8,
                                receivedTTL: UInt8) -> Int? {
        guard originTTL >= receivedTTL else { return nil }
        return Int(originTTL - receivedTTL) + 1
    }
}

```

When a node receives a packet, it compares `receivedTTL` against `originTTL`. The formula `originTTL - receivedTTL + 1` yields the number of hops traversed. Invalid packets—where `receivedTTL > originTTL`—return `nil` from `hopCount()` and are discarded.

## RelayController: The TTL-Based Decision Engine

The heart of BitChat's mesh networking logic resides in [`RelayController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/RelayController.swift). This component encapsulates all forwarding logic into a structured decision format.

### The RelayDecision Struct

```swift
// bitechat/Services/RelayController.swift
struct RelayDecision {
    let shouldRelay: Bool
    let newTTL: UInt8
    let delayMs: UInt64
}

```

Each incoming packet triggers evaluation across multiple factors:

- **Remaining TTL** — packets reaching TTL `0` are never forwarded
- **Network density** — dense regions apply `TransportConfig.bleFragmentRelayTtlCapDense` to clamp TTL
- **Message type** — certain packets like `REQUEST_SYNC` bypass routing regardless of TTL headroom
- **Fragment size** — large BLE fragments face stricter caps via `TransportConfig.bleFragmentRelayTtlCap`
- **Delay budgeting** — optional back-off prevents broadcast storms

### TTL Decrement and Clamping Logic

When forwarding is approved, the controller calculates the new TTL:

```swift
// Inside RelayController.swift (simplified)
let newTTL = max(0, incomingTTL &- 1)                 // normal hop decrement
return RelayDecision(shouldRelay: true,
                     newTTL: newTTL,
                     delayMs: 0)

```

The bitwise `&-` operator performs saturating subtraction, ensuring clean handling of edge cases.

## End-to-End Message Flow

Understanding the complete propagation path clarifies how TTL-based relay decisions operate in practice:

1. **Origin node** creates a packet with `originTTL = TransportConfig.messageTTLDefault` (typically 7)
2. **First-hop recipient** receives TTL 7, computes hop count of 1, and queries `RelayController`
3. Controller returns `shouldRelay = true` with `newTTL = 6`; packet is re-encoded and broadcast
4. **Subsequent hops** repeat the process, decrementing TTL until reaching 0
5. **TTL exhaustion** triggers `shouldRelay = false`, halting propagation

This mechanism guarantees **deterministic termination**—no message can circulate indefinitely.

## Adaptive Network Behavior

BitChat's TTL-based mesh networking adapts dynamically to environmental conditions without requiring global coordination.

### Density-Aware Throttling

In dense network regions, the controller applies lower TTL caps automatically:

```swift
// Conceptual usage within RelayController
let denseRegionCap = TransportConfig.bleFragmentRelayTtlCapDense
let effectiveCap = min(receivedTTL, denseRegionCap)
let newTTL = effectiveCap > 0 ? effectiveCap - 1 : 0

```

This prevents **broadcast storms** in crowded environments while preserving reachability in sparse regions.

### Security Considerations

The packet signature in [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift) is generated with a **fixed TTL of 0**, as noted in source comments. This architectural choice allows relays to modify the TTL field without invalidating cryptographic authenticity—each hop can safely decrement TTL without breaking the signature chain.

## Practical Implementation Examples

### Creating a TTL-Enabled Mesh Ping

```swift
import BitFoundation

let nonce = Data([0x01, 0x02, 0x03, 0x04])
let ttl: UInt8 = TransportConfig.messageTTLDefault   // usually 7
guard let payload = MeshPingPayload(nonce: nonce,
                                   originTTL: ttl) else {
    fatalError("Invalid payload")
}

```

### Making Relay Decisions on Incoming Packets

```swift
import Bitchat

func handleIncoming(_ packet: BitchatPacket) async {
    let decision = RelayController.makeDecision(
        ttl: packet.ttl,
        packetType: packet.type,
        networkDegree: currentDegree,
        highDegreeThreshold: 15)
    guard decision.shouldRelay else { return }

    var forwarded = packet
    forwarded.ttl = decision.newTTL
    await BLEBroadcaster.broadcast(forwarded,
                                   afterDelayMs: decision.delayMs)
}

```

### Computing Traversed Hop Count

```swift
let hops = MeshPingPayload.hopCount(originTTL: packet.originTTL,
                                   receivedTTL: packet.ttl)
print("Packet traversed \(hops ?? 0) hops")

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`localPackages/BitFoundation/Sources/BitFoundation/MeshPingPayload.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/MeshPingPayload.swift) | Defines `originTTL` field and `hopCount()` calculation |
| [`bitechat/Services/RelayController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitechat/Services/RelayController.swift) | Core TTL-based relay decision engine |
| [`bitechat/Services/BLE/BLEReceivePipeline.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitechat/Services/BLE/BLEReceivePipeline.swift) | Invokes `RelayController` for each inbound BLE packet |
| [`localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift) | Packet structure with TTL-agnostic signature generation |
| [`bitechat/Config/TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitechat/Config/TransportConfig.swift) | Default TTL values and environment-specific caps |

## Summary

- **TTL fields enable hop-limited propagation** without central coordination
- **`RelayDecision` encapsulates forwarding logic** including TTL decrement, clamping, and delay scheduling
- **Density-aware caps prevent broadcast storms** in crowded network regions
- **Fixed-TTL signatures preserve security** while allowing relay modifications
- **Autonomous node decisions scale efficiently** across heterogeneous mesh topologies

## Frequently Asked Questions

### How does BitChat prevent infinite packet circulation?

BitChat prevents infinite circulation through TTL exhaustion. Each relay decrements the TTL by at least one. When TTL reaches zero, `RelayController.makeDecision()` returns `shouldRelay = false`, permanently halting that packet's propagation. The maximum hop count equals the `originTTL` value set by the sender.

### What happens when received TTL exceeds origin TTL?

The `MeshPingPayload.hopCount()` function returns `nil` when `receivedTTL > originTTL`, signaling an invalid or corrupted packet. Receiving nodes should discard such packets immediately, as they violate the protocol's ordering invariants.

### Can relays modify TTL without breaking packet signatures?

Yes. According to comments in [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift), signatures are generated with a fixed TTL of 0. This design choice explicitly allows relays to decrement TTL fields without invalidating cryptographic proofs, enabling secure hop-by-hop forwarding.

### How does network density affect relay decisions?

Dense networks trigger TTL clamping via `TransportConfig.bleFragmentRelayTtlCapDense`. The `RelayController` compares current node degree against `highDegreeThreshold` and applies lower TTL caps when density exceeds configured limits, reducing redundant transmissions without requiring global topology knowledge.