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

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, 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, the service initializes with dependencies including NoiseEncryptionService, SecureIdentityStateManager, and KeychainManager. The start() method activates central and peripheral managers to begin simultaneous advertising and scanning.

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.

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, which tracks connection state, MTU size, RSSI values, and authentication status.

The BLELinkStateStore maintains mutable state for every physical link, while 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 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 manages the transmission queue for outbound fragments, respecting BLE flow-control characteristics to prevent buffer overflows.

On the receiving side, 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.

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 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 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, 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. The pingMesh method sends test packets through the mesh to calculate round-trip time and hop count, helping identify connectivity bottlenecks.

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 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 before transmission. The receiving peer’s BLEFragmentAssemblyBuffer.swift collects and reassembles these fragments before passing the complete packet to the deduplication layer, ensuring reliable delivery of arbitrary message sizes.

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 →