# How BitChat Manages State Across BLE Architecture Layers

> Discover how BitChat manages state across BLE architecture layers using isolated queues and thread-safe events. Learn about BLELinkLayer, messageQueue, and feature module communication for deadlock-free operation.

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

---

**BitChat uses a strictly layered BLE stack where each layer owns isolated state on dedicated queues—BLELinkLayer on `bleQueue`, the mesh engine on `messageQueue`, and feature modules via handlers—communicating through thread-safe events and commands to eliminate deadlocks.**

State management in Bluetooth Low Energy (BLE) applications often becomes complex due to asynchronous hardware events and threading constraints. In the permissionlesstech/bitchat repository, the architecture solves this by enforcing strict state ownership across four distinct layers, ensuring that state is never shared across queue boundaries. This design eliminates deadlocks by construction while maintaining deterministic behavior for mesh networking protocols.

## Layered State Ownership in the BitChat BLE Architecture

BitChat organizes its BLE transport into four distinct layers, each with well-defined state responsibilities and dedicated execution contexts. This separation ensures that the mesh engine remains a pure **sans-I/O** core while the link layer handles all CoreBluetooth interactions.

### BLELinkLayer and Physical Link State

The `BLELinkLayer` is the only component that imports CoreBluetooth, making it the sole owner of physical link state. It manages `CBPeripheral` and `CBCharacteristic` objects, MTU configuration, write buffers, and notification back-pressure on the dedicated **`bleQueue`** (a serial queue for BLE operations). According to the source in [`BLELinkStateStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkStateStore.swift) [lines 37-44](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLELinkStateStore.swift#L37-L44), this store captures all physical connection state and provides the `assumeOwnership` method to trap illegal access attempts in debug builds.

### Mesh Engine and Protocol State

Above the link layer sits the mesh engine, a pure computation layer that serializes all mesh protocol state including codec logic, packet fragmentation, deduplication, relay policy, and Noise cryptography orchestration. The engine maintains engine-confined stores such as `BLELinkAuthState`, `BLELinkBindings`, and the deduplication cache, all accessed exclusively on the **`messageQueue`**. As implemented in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) [lines 71-78](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift#L71-L78), this queue isolates the engine from I/O, allowing it to process `LinkEvent` inputs and emit `LinkCommand` outputs as side effects.

### Feature Modules and Domain State

Feature modules—including Courier, Board, Private Media, Voice, and Groups—own their specific state stores (e.g., `BLEPrivateMediaSessionStore`, `BLEMeshPingTracker`). Rather than running on separate threads, these modules register handlers (`BLEFragmentHandler`, `BLEPublicMessageHandler`, etc.) that execute on the **engine queue**. This design ensures that module state mutations remain engine-confined while allowing specialized domains to manage their own data structures.

### App Boundary Facade

The `Transport` façade exposes capability protocols (`MeshBridgingTransport`, `PanicResettingTransport`, `BluetoothStateReporting`) to the UI layer. This boundary is stateless; calls from the main thread always hop onto the appropriate queue via `onEngine` or `bleQueue` rather than accessing underlying stores directly.

## Concurrency Contracts and Queue Isolation

The architecture defines three strict ownership models detailed in the **BLE-ARCHITECTURE-V3** design document. These contracts prevent race conditions by ensuring state is never shared across queue boundaries.

### Engine-Confined State

All mesh engine state is **engine-confined**, meaning it mutates only on `messageQueue`. Callers from other threads use the `onEngine` method implemented in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) [lines 96-106](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift#L96-L106) to synchronously enter the engine context. This pattern guarantees that packet processing, cryptographic operations, and registry updates happen atomically relative to the engine's event loop.

### BLEQueue-Confined State

The link layer's physical state and any buffers touching CoreBluetooth are **bleQueue-confined**. Direct access outside `bleQueue` triggers assertions in debug builds through the `assumeOwnership` checks. This isolation protects CoreBluetooth's thread-unsafe APIs from concurrent access while allowing the link layer to manage back-pressure and connection scheduling independently.

### Lock-Backed Stores for Cross-Queue Reads

Some data, such as peer snapshots, must be readable from the main actor while only the engine mutates it. The `BLEPeerRegistryStore` implements this pattern using lock protection, as shown in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) [lines 42-48](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift#L42-L48). The main thread can safely query peer registry data without queue hops, while the engine updates the registry on `messageQueue` under the same lock.

## State Flow Between Architecture Layers

State changes propagate through the stack via explicit events and commands, following a strict **sync-edge order** of `main → engine → bleQueue`. No layer ever synchronously waits in the reverse direction, eliminating circular dependencies.

1. **Physical events** (peripheral connections, characteristic notifications) arrive on `bleQueue`.
2. The link layer updates `BLELinkStateStore` and emits a `LinkEvent` upward.
3. `BLEService` consumes the event on `messageQueue` via `onEngine`, querying or updating engine-confined stores like `BLELinkAuthState`.
4. Feature module handlers process decoded packets, updating their specific stores (e.g., `BLEPublicMessageHandler` updating message state).
5. Outbound work (writes, notifications) generates `LinkCommand` objects that descend to the link layer and queue on `bleQueue` for physical transmission.

## Practical Example: Sending a Message Through the Layers

The following Swift code demonstrates how state management operates when sending a public message, showing the queue-hopping contract from UI to physical layer:

```swift
// 1. Called from UI (main thread)
chatViewModel.sendMessage("Hello world")
// 2. ChatViewModel forwards to BLEService.sendMessage (still on main)
BLEService.sendMessage(...)

// Inside BLEService.sendMessage:
if DispatchQueue.getSpecific(key: messageQueueKey) == nil {
    // 3. Hop onto the engine queue
    messageQueue.async { self.sendMessage(...) }
    return
}

// 4. Engine-confined path – sign packet, dedup, broadcast
let signed = noiseService.signPacket(basePacket)
messageDeduplicator.markProcessed(...)
broadcastPacket(signed)

// 5. broadcastPacket ultimately calls writeOrEnqueue on bleQueue
bleQueue.async { … }   // physical write happens here

```

This path demonstrates the three-queue contract: UI → `messageQueue` (engine) → `bleQueue` (link-layer). Each hop transitions state ownership cleanly, with the engine handling cryptographic signing and deduplication before the link layer manages the physical write buffers.

## Key Source Files for State Management

Understanding BitChat's state architecture requires examining these specific implementation files:

- **[`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift)** – Central orchestrator that defines the queue architecture, implements `onEngine` synchronization, and exposes the public API.
- **[`BLELinkStateStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkStateStore.swift)** – Contains physical link state and `bleQueue` confinement logic, including ownership assertions.
- **[`BLELinkAuthState.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkAuthState.swift)** – Engine-confined store for authentication state and rebind-containment data.
- **[`BLEPeerRegistryStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEPeerRegistryStore.swift)** – Lock-backed store allowing main-thread reads of peer data while maintaining engine-queue writes.
- **[`BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/BLE-ARCHITECTURE-V3.md)** – Design document specifying the four-layer stack and concurrency contracts.
- **Feature handlers** ([`BLEPublicMessageHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEPublicMessageHandler.swift), [`BLEFragmentHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentHandler.swift), etc.) – Modules owning feature-specific state and registering with the engine.

## Summary

BitChat's BLE architecture achieves deterministic state management across different layers through strict isolation and queue ownership:

- **BLELinkLayer** owns all physical state on `bleQueue`, the only thread touching CoreBluetooth objects.
- **Mesh engine** processes all protocol logic on `messageQueue` using engine-confined stores, operating as a sans-I/O core.
- **Feature modules** maintain domain-specific state through handlers executing on the engine queue.
- **Lock-backed stores** like `BLEPeerRegistryStore` provide safe read access for the main actor without breaking queue isolation.
- **Unidirectional sync edges** (`main → engine → bleQueue`) guarantee deadlock-free operation by construction.

## Frequently Asked Questions

### How does BitChat prevent deadlocks between BLE layers?

BitChat prevents deadlocks by enforcing a strict **sync-edge order** where state access only flows from `main → engine → bleQueue`. No layer ever synchronously waits for a layer below it to complete work. Instead, communication happens through asynchronous events (`LinkEvent`) and commands (`LinkCommand`), ensuring that callbacks never traverse upward in the stack during synchronous execution.

### What is the role of BLELinkStateStore in the architecture?

`BLELinkStateStore` serves as the single source of truth for physical BLE state, including `CBPeripheral` connections, `CBCharacteristic` references, MTU settings, and back-pressure buffers. It operates exclusively on `bleQueue` and includes `assumeOwnership` assertions to trap illegal cross-thread access attempts in debug builds, protecting CoreBluetooth's thread-unsafe APIs.

### How can the main thread safely read peer registry data without causing race conditions?

The main thread accesses peer data through `BLEPeerRegistryStore`, which implements a **lock-backed store** pattern. While the engine mutates registry data on `messageQueue` under a lock, the main actor can safely query snapshots using the same lock for reading. This design, declared in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) lines 42-48, avoids queue hops for UI reads while maintaining engine-queue isolation for writes.

### Why does the mesh engine use a sans-I/O design?

The mesh engine uses a **sans-I/O** design (handling `event -> [Effect]`) to ensure deterministic testing and thread safety. By removing all direct Bluetooth I/O from the engine and isolating it on `messageQueue`, the core logic becomes purely functional and testable without hardware dependencies. All side effects—such as writing to characteristics—are emitted as `LinkCommand` values that the link layer executes asynchronously on `bleQueue`.