How BitChat's BLE Mesh Network Enables Offline Communication
BitChat implements a Bluetooth Low Energy mesh network that functions as a store-and-forward packet radio system, allowing messages to be stored locally and delivered when recipients reconnect.
BitChat, an open-source messaging application developed by permissionlesstech, leverages a custom BLE mesh network to enable communication without internet connectivity. This article explains how the mesh architecture processes, routes, and stores messages for offline delivery using the Swift implementation found in the permissionlesstech/bitchat repository.
BitChat BLE Mesh Architecture
The BitChat BLE mesh network is organized into a four-layer architecture that separates concerns between radio hardware, protocol logic, feature modules, and the application interface.
Four-Layer Stack
-
BLE Link Layer: Handles direct CoreBluetooth interaction including scanning, advertising, connection scheduling, and MTU management. Key implementations include
BLELinkStateStoreandBLERadioControllerinbitchat/Services/BLE/BLERadioController.swift. -
Mesh Engine: Contains the serial-queue core that owns all protocol state including packet codecs, fragmentation, deduplication, and relay policies. This layer implements the primary
handle(event) -> [Effect]loop inBLEServicelocated atbitchat/Services/BLE/BLEService.swift, utilizing helpers likeBLEFragmentAssemblyBufferandBLEMeshPingTracker. -
Feature Modules: Encapsulate specific capabilities such as courier services, message boards, and file transfer. Each module owns its state independently and registers for specific packet types without accessing engine internals. Examples include
CourierStore,BoardStore, andPrekeyBundleStore. -
App Boundary: Provides a thin
Transportfaçade that the UI consumes throughTransportprotocol extensions onBLEService. This layer handles capability discovery and forwards high-level events to view models while abstracting mesh complexity.
Store-and-Forward Flow for Offline Communication
When a recipient device is unreachable, BitChat's mesh engine queues messages in the CourierStore, creating a store-and-forward system that functions similarly to packet radio networks.
Message Transmission Pipeline
-
Message Creation: The UI invokes
sendMessage(to:payload:ttl:completion:)on theTransportprotocol, which is implemented byBLEService. -
Fragmentation: Payloads exceeding the BLE MTU are split by
BLEFragmentAssemblyBufferinto fragments queued inBLEOutboundFragmentTransferScheduler. -
Routing Decision: The engine selects a next-hop based on
MeshTopologyTrackerand gossip policy thresholds. If the destination peer is unreachable, the packet routes toCourierStorefor offline storage. -
Encryption: Each fragment receives Noise protocol encryption via
NoiseSessionManager, binding the fragment to the specific physical link that authenticated it throughBLELinkAuthStateandBLELinkBindings. -
Transmission:
BLELinkLayerwrites bytes to BLE peripherals or centrals, respecting back-pressure buffers includingpendingPeripheralWritesandpendingNotifications. -
Reception: Remote devices receive fragments through the link layer, which hands raw bytes to the engine's
ingestDecodedPacketmethod for reassembly and Noise tag validation. -
Delivery or Storage: If the destination is online, the engine forwards the complete packet to feature modules via
BitchatDelegate. If offline, the envelope persists inCourierStore—a lock-backed, main-actor cache that respects per-peer quotas defined byCourierDepositTieruntil the recipient reconnects.
Key Source Files and Components
Understanding the implementation requires familiarity with these specific files in the repository:
-
bitchat/Services/BLE/BLEService.swift: Central class implementing the mesh engine queue, peer registry, link state management, and courier store integration. -
bitchat/Services/BLE/BLERadioController.swift: Implements the link layer that emitsLinkEventobjects and consumesLinkCommandobjects while managing CoreBluetooth interactions. -
bitchat/Services/BLE/BLELinkStateStore.swift: Maintains per-link connection states and back-pressure buffers for unreliable link management. -
bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift: Handles packet fragmentation and reassembly when payloads exceed BLE MTU limitations. -
bitchat/Services/Courier/CourierStore.swift: Persistent store-and-forward cache enabling offline message retention with quota management. -
docs/BLE-ARCHITECTURE-V3.md: Design document detailing the layered stack, concurrency contracts, and mesh topology algorithms.
Practical Implementation Examples
Sending an Offline-Capable Message
To send a message that will be stored for offline delivery if necessary:
// Transport is a BLEService instance conforming to the Transport protocol
let transport: Transport = bleService
let payload = "Hello offline world!".data(using: .utf8)!
transport.sendMessage(
to: .broadcast,
payload: payload,
ttl: TransportConfig.messageTTLDefault
) { result in
switch result {
case .success:
print("Queued for mesh delivery or offline storage")
case .failure(let error):
print("Transmission failed: \(error)")
}
}
This call traverses BLEService into BLEOutboundFragmentTransferScheduler, then BLELinkLayer. If no peers accept the packet, the engine stores it in CourierStore as implemented in BLEService around lines 258-260.
Receiving Messages via Delegate
Implement BitchatDelegate to handle incoming messages after the mesh engine verifies and reassembles them:
class ChatViewModel: BitchatDelegate {
func didReceivePublicMessage(_ packet: BitchatPacket) {
// Update UI with verified payload
displayMessage(packet.payload)
}
}
// Assign delegate to receive events exclusively through this interface
bleService.delegate = self
The delegate receives packets only after the engine successfully validates Noise tags and reassembles fragments via BLEFragmentAssemblyBuffer.
Accessing Offline Storage
When a peer reconnects, retrieve and attempt delivery of stored messages:
func handlePeerConnected(_ peerID: PeerID) {
let pendingEnvelopes = CourierStore.shared.envelopesForPeer(peerID)
pendingEnvelopes.forEach { envelope in
bleService.tryDeliver(envelope)
}
CourierStore.shared.removeDelivered(envelopes: pendingEnvelopes)
}
CourierStore enforces CourierDepositTier quotas and provides lock-backed access suitable for main-actor consumption without blocking the mesh engine.
Concurrency and Safety Architecture
The mesh engine guarantees thread safety through strict queue separation. The engine queue operates serially, ensuring all state mutations—including peer registry updates, routing table changes, and Noise session management—occur without locks, preventing deadlocks. The link-layer queue handles only I/O operations and never touches engine state, maintaining a one-way synchronization edge from main to engine to bleQueue as documented in BLE-ARCHITECTURE-V3.md.
Deterministic Simulation and Testing
BitChat validates its offline logic through SimulatedLinkLayer, allowing the mesh engine to run off-device. The test suite in bitchatTests/Simulation/SimulatedMesh.swift drives the identical code paths used in production without real radios, proving that store-and-forward logic functions correctly even when devices remain out of range for extended periods.
Summary
- BitChat implements a four-layer BLE mesh architecture separating radio hardware from protocol logic and application interfaces.
- Offline communication works via
CourierStore, a persistent cache that retains messages when recipients are unreachable and delivers them upon reconnection. - Fragmentation and encryption occur at the mesh engine layer using
BLEFragmentAssemblyBufferandNoiseSessionManager, ensuring data integrity across multiple hops. - Strict concurrency controls use serial queues to eliminate deadlocks while maintaining high-throughput packet processing.
- Comprehensive simulation via
SimulatedMeshvalidates offline behavior without hardware dependencies.
Frequently Asked Questions
How does BitChat handle message delivery when the recipient is completely out of range?
When the recipient is unreachable, the mesh engine stores the message in CourierStore, a lock-backed cache that persists envelopes until the destination peer reconnects. The store enforces per-peer quotas through CourierDepositTier to prevent resource exhaustion, ensuring messages survive temporary network partitions.
What encryption does BitChat use for mesh messages?
BitChat implements the Noise protocol through NoiseSessionManager, binding each packet fragment to the specific authenticated link that transmitted it. This link-bound encryption in BLELinkAuthState ensures fragments are only accepted on the physical link that originally authenticated them, preventing interception on alternate paths.
Can BitChat's mesh network simulate offline scenarios for testing?
Yes, the codebase includes SimulatedLinkLayer and SimulatedMesh in the test suite, which execute the identical mesh engine logic without hardware. These simulations validate store-and-forward behavior, fragmentation, and routing decisions in deterministic environments where devices are programmatically taken offline and brought back online.
What is the maximum message size supported by the BLE mesh?
The mesh handles payloads exceeding standard BLE MTU through BLEFragmentAssemblyBuffer, which automatically fragments large messages and reassembles them at the destination. While specific size limits depend on available CourierStore quota and memory constraints, the fragmentation layer abstracts MTU limitations from the application layer.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →