# How BitChat's Bluetooth Mesh Layer Works: Core Bluetooth Implementation Guide

> Discover how BitChat implements its Bluetooth Mesh Layer using Core Bluetooth. Learn about its custom BLEService, decentralized networking, and offline peer-to-peer messaging.

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

---

**BitChat uses a custom BLEService class built on Apple’s Core Bluetooth framework to create a decentralized mesh network where devices advertise a custom GATT service, perform Noise-based authentication, and fragment messages across serial queues to enable offline peer-to-peer messaging.**

BitChat’s Bluetooth mesh layer enables decentralized messaging between nearby iOS devices without internet connectivity. The implementation centers on the `BLEService` class in [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift), which orchestrates advertising, scanning, encryption, and routing across a dedicated serial queue architecture. This article examines the technical mechanisms that allow BitChat to discover peers, maintain secure links, and route messages through multi-hop Bluetooth Low Energy connections.

## Core Architecture and Threading Model

The mesh layer operates on a strict concurrency model to maintain low-latency BLE operations while ensuring thread safety. All Bluetooth operations execute on a dedicated `bleQueue`, while higher-level engine state is marshaled onto the `messageQueue`.

In [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift), the service initializes with dependencies including `NoiseEncryptionService`, `SecureIdentityStateManager`, and `KeychainManager`. The `start()` method activates central and peripheral managers to begin simultaneous advertising and scanning.

```swift
let bleService = BLEService(
    noiseService: NoiseEncryptionService(),
    noiseResponderHandshakeTimeout: TransportConfig.noiseResponderHandshakeTimeout,
    identityManager: SecureIdentityStateManager(),
    keychain: KeychainManager(),
    idBridge: NostrIdentityBridge()
)
bleService.start()          // starts central & peripheral managers, begins scanning/advertising

```

This dual-mode approach allows every device to act as both a BLE peripheral (advertising) and central (scanning), creating a true peer-to-peer mesh rather than a client-server topology.

## Peer Discovery and Link State Management

BitChat discovers peers by advertising a custom GATT service UUID and scanning for identical advertisements from other devices. Each discovered peripheral triggers the creation of a link entry in [`BLELinkStateStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkStateStore.swift), which tracks connection state, MTU size, RSSI values, and authentication status.

The `BLELinkStateStore` maintains mutable state for every physical link, while [`BLELinkAuthState.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkAuthState.swift) handles the cryptographic handshake phase. Only authenticated links participate in message forwarding, preventing unauthorized nodes from injecting traffic into the mesh.

## Security: Noise Protocol Handshake

When two devices establish a physical connection, they execute a Noise-based cryptographic handshake via `NoiseEncryptionService`. The implementation in [`BLELinkAuthState.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkAuthState.swift) stores the handshake result, deriving session keys that encrypt all subsequent mesh traffic.

This authentication occurs before any message data flows. The mesh layer drops connections that fail to complete the handshake within the configured timeout (`noiseResponderHandshakeTimeout`), ensuring that only cryptographically verified peers exchange messages.

## Message Fragmentation and Reassembly

Bluetooth LE packets are limited to approximately 512 bytes MTU, requiring BitChat to fragment larger messages. The [`BLEOutboundFragmentTransferScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundFragmentTransferScheduler.swift) manages the transmission queue for outbound fragments, respecting BLE flow-control characteristics to prevent buffer overflows.

On the receiving side, [`BLEFragmentAssemblyBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentAssemblyBuffer.swift) collects incoming fragments and reconstructs the original `BitchatPacket`. This reassembly occurs on the `bleQueue` before the complete message transfers to the deduplication layer.

To send a message across the mesh, higher-level components like `ChatViewModel` invoke `sendMeshPayload()`, which handles encryption, fragmentation, and queueing internally.

```swift
let payload = BitchatPacket(message: myMessage, ttl: 5)
bleService.sendMeshPayload(payload)   // fragmenting, encrypting, and queueing is handled internally

```

## Mesh Topology and Routing Decisions

The [`MeshTopologyTracker.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshTopologyTracker.swift) maintains a lightweight directed graph of reachable peers, tracking which nodes are directly connected versus reachable through relays. This tracker drives routing decisions, preferring direct Bluetooth connections over multi-hop relays to minimize latency.

When a message must traverse multiple hops, the topology tracker consults the connectivity graph to select the next hop. The component also provides data models to `MeshTopologyView`, allowing the UI to visualize the current mesh structure.

## Reliability Mechanisms

### Message Deduplication

The `MessageDeduplicator` in [`bitchat/Utils/MessageDeduplicator.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Utils/MessageDeduplicator.swift) filters duplicate packets caused by retransmissions or multi-path routing. This prevents the upper layers from processing the same message multiple times when it arrives via different routes.

### Store-and-Forward (Courier)

When a target peer is offline, BitChat stores messages in `CourierStore`, a local persistence layer. Upon detecting peer reconnection through the link state store, the service automatically forwards queued messages, providing asynchronous messaging capabilities despite intermittent connectivity.

### Error Handling and Route Failure

The mesh tracks failed routing attempts in [`BLESourceRouteFailureCache.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLESourceRouteFailureCache.swift), temporarily blacklisting routes that consistently fail. This cache prevents wasteful retransmission attempts through broken links, allowing the topology tracker to explore alternative paths.

## Battery Optimization and Rate Limiting

To preserve device battery and reduce radio congestion, BitChat implements multiple throttling mechanisms. `BLESubscriptionAnnounceLimiter` and `BLEAnnounceThrottle` regulate how frequently devices broadcast presence announcements. Additionally, adaptive TTL logic (`highDegreeThreshold`) reduces broadcast radius for well-connected nodes, minimizing redundant traffic in dense mesh networks.

## Diagnostic Monitoring

Developers can measure mesh health using the diagnostic tools in [`BLEMeshPingTracker.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEMeshPingTracker.swift). The `pingMesh` method sends test packets through the mesh to calculate round-trip time and hop count, helping identify connectivity bottlenecks.

```swift
bleService.pingMesh { result in
    switch result {
    case .success(let ping):
        print("RTT: \(ping.rttMs) ms, hops: \(ping.hops)")
    case .failure(let err):
        print("Ping failed: \(err)")
    }
}

```

The service also surfaces connectivity status to UI components like `ConnectivityStatusBanner`, providing real-time feedback on link quality and mesh participation.

## Summary

BitChat’s Bluetooth mesh layer combines Core Bluetooth primitives with cryptographic authentication and intelligent routing to create a resilient offline messaging network:

- **Concurrent Architecture**: Uses `bleQueue` for low-latency BLE operations and `messageQueue` for thread-safe engine state.
- **Security-First**: Enforces Noise-based authentication via `BLELinkAuthState` before allowing message exchange.
- **Robust Transport**: Fragments large messages using `BLEOutboundFragmentTransferScheduler` and `BLEFragmentAssemblyBuffer` to handle MTU limitations.
- **Smart Routing**: `MeshTopologyTracker` maintains peer graphs and optimizes paths across the decentralized mesh.
- **Store-and-Forward**: `CourierStore` enables asynchronous messaging when recipients are temporarily offline.

## Frequently Asked Questions

### How does BitChat authenticate new peers joining the mesh?

BitChat requires every new peer to complete a Noise-based cryptographic handshake before exchanging messages. The `BLELinkAuthState` class in [`bitchat/Services/BLE/BLELinkAuthState.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLELinkAuthState.swift) manages this process using the `NoiseEncryptionService`, ensuring that only cryptographically verified devices establish authenticated links capable of forwarding traffic.

### Can BitChat deliver messages when the recipient is offline?

Yes. BitChat implements store-and-forward semantics through the `CourierStore`. When the `MeshTopologyTracker` detects that a target peer is disconnected, outgoing messages are persisted locally and automatically forwarded once the peer reconnects and the link state transitions to authenticated.

### How does BitChat prevent battery drain in dense mesh environments?

The mesh layer uses `BLESubscriptionAnnounceLimiter` and `BLEAnnounceThrottle` to reduce broadcast frequency, combined with adaptive TTL logic that limits announcement radius for nodes with high connectivity. These mechanisms minimize radio usage while maintaining network coherence, as configured in the transport layer settings.

### What happens if a message exceeds the Bluetooth MTU size?

Messages larger than the BLE MTU (approximately 512 bytes) are automatically fragmented by [`BLEOutboundFragmentTransferScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundFragmentTransferScheduler.swift) before transmission. The receiving peer’s [`BLEFragmentAssemblyBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentAssemblyBuffer.swift) collects and reassembles these fragments before passing the complete packet to the deduplication layer, ensuring reliable delivery of arbitrary message sizes.