BitChat BLE Architecture V3: The Four Layers Explained
The BitChat V3 Bluetooth Low Energy transport implements four distinct architectural layers—BLELinkLayer, Mesh Engine, Feature Modules, and App Boundary—that enforce strict concurrency contracts and enable deterministic testing without I/O dependencies.
BitChat is an open-source peer-to-peer messaging protocol designed for permissionless, offline-first communication. The BitChat BLE architecture V3, documented in docs/BLE-ARCHITECTURE-V3.md, reorganizes the stack into isolated layers that separate CoreBluetooth primitives from mesh protocol logic and application-facing interfaces.
The Four Layers of the BitChat BLE Stack
The architecture establishes clear boundaries between hardware abstraction, protocol state, feature logic, and application integration. Each layer operates under specific confinement rules that prevent deadlock and enable unit testing.
1. BLELinkLayer
The BLELinkLayer serves as the sole interface to CoreBluetooth. It owns all scanning and advertising operations, duty-cycle management, connection scheduling, MTU negotiation, and write/notification back-pressure buffers. This layer emits LinkEvent instances (link-up/down, bytes-in, writable state changes) and consumes LinkCommand directives (send bytes, adjust scan/advertise policies).
Critically, no packet parsing, peer management, or Noise protocol logic exists here. The layer is Ble-queue confined, meaning it is the only component in the codebase that imports CoreBluetooth, ensuring that all hardware interactions remain isolated from business logic.
2. Mesh Engine
The Mesh Engine functions as the serial-queue-confined core of the mesh protocol. It manages the wire codec, packet fragmentation, deduplication, relay policy, peer registry, topology maintenance, gossip synchronization, and Noise handshake orchestration.
All state mutations occur on a single serial engine queue through a deterministic handle(event:now:) -> [Effect] function signature. This design renders the engine pure-logic and fully testable without I/O dependencies, as defined in BLEEngineCore.swift and BLEEnginePolicy.swift.
3. Feature Modules
Feature Modules implement individual capabilities such as courier messaging, public boards, pre-keys, private media, file transfer, voice, diagnostics, groups, and verification. Each module maintains its own isolated state and registers for specific message types it handles.
This modular approach means adding new transport capabilities requires implementing a new module rather than modifying the engine. Examples include BLEMeshPingTracker.swift for latency tracking and BLEPrivateMediaSessionStore.swift for encrypted media handling.
4. App Boundary
The App Boundary provides the thin transport façade that applications consume. It exposes a minimal Transport core alongside capability-discovery protocols such as MeshBridgingTransport and PanicResettingTransport.
This layer replaces the previous monolithic "god-protocol" design with clean, composable interfaces defined in Transport.swift and MeshBridgingTransport.swift, isolating the application from BLE internals while allowing runtime transport selection.
Concurrency Contracts
The BitChat BLE architecture enforces three strict confinement rules that guarantee deadlock-free synchronization:
- Engine-confined: Only the mesh engine mutates its internal state on the serial engine queue
- Ble-queue-confined: The link layer's buffers and CoreBluetooth objects live exclusively on the BLE queue
- Lock-backed stores: Cross-domain readers access the peer registry, identity stores, and traffic statistics through locks, never blocking either queue
Communication flows unidirectionally from engine to BLE queue, never reversing, which eliminates circular wait conditions.
Implementation Examples
The following Swift snippets demonstrate typical interactions with each architectural layer.
BLELinkLayer Command Issuance
let linkLayer = BLELinkLayer()
linkLayer.sendCommand(.startScanning)
This emits a LinkCommand that translates to CoreBluetooth operations within the Ble-queue confined context.
Mesh Engine Event Processing
engineQueue.async {
let effects = meshEngine.handle(event: .packetReceived(packet, linkID), now: Date())
effects.forEach { $0.apply() }
}
The handle(event:now:) method processes inputs deterministically on the engine queue, returning effects that modify state or trigger I/O.
Feature Module Registration
privateMediaModule.register { packet in
processPrivateMedia(packet)
}
Feature modules expose registration APIs that bind message types to isolated handling logic without engine modification.
App Boundary Transport Usage
transport.sendMessage(
payload: .text("Hello world"),
to: recipient,
preferredTransport: .bluetoothFirst
)
The Transport façade selects BLE first with automatic fallback to Nostr, hiding complexity behind the App Boundary interface.
Key Source Files
The following files implement the four-layer architecture:
| Layer | Source Files | Responsibility |
|---|---|---|
| BLELinkLayer | BLELinkLayer.swift |
CoreBluetooth abstraction and queue management |
| Mesh Engine | BLEEngineCore.swift, BLEEnginePolicy.swift |
Serial queue execution and protocol policies |
| Feature Modules | BLEMeshPingTracker.swift, BLEPrivateMediaSessionStore.swift |
Isolated capability implementations |
| App Boundary | Transport.swift, MeshBridgingTransport.swift |
Application-facing interface definitions |
Summary
- The BitChat BLE architecture V3 separates concerns into four distinct layers: BLELinkLayer, Mesh Engine, Feature Modules, and App Boundary
- BLELinkLayer isolates all CoreBluetooth interactions within a Ble-queue confined context
- The Mesh Engine provides deterministic, testable protocol logic through a serial queue and pure effect-based state management
- Feature Modules enable extensibility without engine modification, each owning isolated state for specific capabilities
- The App Boundary exposes minimal, composable interfaces that hide transport complexity from applications
- Strict concurrency contracts prevent deadlocks by enforcing unidirectional data flow and lock-backed cross-domain access
Frequently Asked Questions
What is the purpose of separating BLELinkLayer from the Mesh Engine?
BLELinkLayer isolates all hardware-specific CoreBluetooth operations, allowing the Mesh Engine to remain platform-agnostic and fully testable without actual Bluetooth hardware. This separation ensures that protocol bugs can be reproduced and fixed in the engine without involving iOS Bluetooth stack complexity.
How does the App Boundary differ from previous BitChat transport implementations?
Earlier versions used a monolithic "god-protocol" that exposed all transport capabilities through a single massive interface. The V3 App Boundary replaces this with capability-discovery protocols like MeshBridgingTransport and PanicResettingTransport, allowing applications to compose only the transport features they need while maintaining binary compatibility.
Why does the Mesh Engine use an effect-based architecture?
The handle(event:now:) -> [Effect] pattern makes the engine deterministic and side-effect free. Because all state changes are returned as effects rather than applied immediately, the engine can be unit tested by asserting on the returned effect collections without executing actual I/O operations or threading code.
Can Feature Modules communicate directly with BLELinkLayer?
No. Feature Modules operate above the Mesh Engine and communicate through the engine's serial queue. Direct BLELinkLayer access is restricted to the engine, maintaining the concurrency contract that prevents Ble-queue blocking and ensures deadlock-free operation across the stack.
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 →