# How the BitChat BLE Service Coordinates CoreBluetooth Callbacks for Mesh Networking

> Discover how BitChat coordinates CoreBluetooth callbacks using a dedicated queue and thread-safe state management for efficient mesh networking. Learn about BLEService's role in translating events.

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

---

**BitChat's `BLEService` funnels all CoreBluetooth delegate callbacks through a dedicated serial queue, translates low-level Bluetooth events into high-level link events, and maintains thread-safe state through `BLELinkStateStore` to coordinate the mesh engine.**

The `BLEService` class in the [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat) repository serves as the central coordination hub between Apple's CoreBluetooth framework and BitChat's internal mesh engine. By implementing a strict serial dispatch architecture and consolidating all delegate callbacks into a single extension, the service ensures deterministic processing of BLE events while preventing race conditions in peer-to-peer messaging. Understanding how this BLE service coordinates CoreBluetooth callbacks reveals the architectural patterns that enable reliable offline mesh communication.

## Serial Queue Architecture for Thread Safety

All CoreBluetooth operations in BitChat execute on a dedicated serial queue to guarantee thread safety and deterministic ordering of state changes.

In [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) at lines 71-84, the service initializes `bleQueue` as a user-initiated `DispatchQueue`:

```swift
// BLEService.swift#L71-L84
private let bleQueue: DispatchQueue

init(...) {
    self.bleQueue = DispatchQueue(label: "com.bitchat.ble", qos: .userInitiated)
    // ...
    self.centralManager = CBCentralManager(delegate: self, queue: bleQueue)
}

```

By passing `bleQueue` to the `CBCentralManager` initializer, every delegate method—from state changes to characteristic updates—executes on this serial queue rather than the main thread. This design prevents concurrent access to shared link state and eliminates the need for complex locking mechanisms throughout the callback handlers.

## Centralized Delegate Implementation

The service implements both `CBCentralManagerDelegate` and `CBPeripheralDelegate` protocols in a single file extension to consolidate all Bluetooth event handling logic.

`BLEService+LinkLayerCentralRole.swift` (lines 21-34) contains the extension header that adopts these protocols:

```swift
// BLEService+LinkLayerCentralRole.swift#L21-L34
extension BLEService: CBCentralManagerDelegate, CBPeripheralDelegate {
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        // Handle power state changes
    }
    
    func centralManager(_ central: CBCentralManager, 
                       didDiscover peripheral: CBPeripheral, 
                       advertisementData: [String: Any], 
                       rssi RSSI: NSNumber) {
        // Handle peripheral discovery
    }
    // ... additional delegate methods
}

```

This centralized approach ensures that discovery, connection, service exploration, characteristic updates, and write confirmations all flow through a single code path before reaching the mesh engine.

## State Management and Event Translation

The service bridges the gap between CoreBluetooth's asynchronous callbacks and the mesh engine's serial processing queue through a two-phase coordination strategy.

### Link State Store Updates

Upon receiving any Bluetooth event, the service immediately updates `BLELinkStateStore`—the single source of truth for physical link status. According to the source code at lines 45-59 of `BLEService+LinkLayerCentralRole.swift`, callbacks write peripheral connection states, discovered characteristics, and notification assembler status into this thread-safe store:

```swift
// BLEService+LinkLayerCentralRole.swift#L45-L59
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    guard !isPanicSuspended else { 
        cleanupLinkState(for: peripheral)
        return 
    }
    
    linkStateStore.updateConnectionState(peripheral, state: .connected)
    peripheral.discoverServices([bitchatServiceUUID])
}

```

The mesh engine accesses this store via a read-only port, ensuring that state queries never block the Bluetooth processing thread.

### Emitting High-Level Link Events

After updating the link state store, callbacks translate low-level Bluetooth events into semantic "link events" that the mesh engine consumes. At lines 81-86, the service calls `emitLinkEvent(_:)` to forward processed events to the engine's `messageQueue`:

```swift
// BLEService+LinkLayerCentralRole.swift#L81-L86
private func emitLinkEvent(_ event: LinkEvent) {
    messageQueue.async { [weak self] in
        self?.engine?.processLinkEvent(event)
    }
}

```

This separation keeps the `bleQueue` lightweight—performing only BLE bookkeeping—while the cryptographic, routing, and deduplication work happens on the engine's dedicated queue.

## Radio Control and Connection Policies

The service delegates scanning and connection policy decisions to `BLERadioController` to maintain clean separation between protocol handling and connection management.

At lines 84-100 of `BLEService+LinkLayerCentralRole.swift`, callbacks forward status changes to the radio controller:

```swift
// BLEService+LinkLayerCentralRole.swift#L84-L100
func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {
    case .poweredOn:
        radio.startScanning()
    case .poweredOff:
        radio.handlePowerOff()
    @unknown default:
        radio.handleErrorState(central.state)
    }
}

func centralManager(_ central: CBCentralManager, 
                   didDiscover peripheral: CBPeripheral, 
                   advertisementData: [String: Any], 
                   rssi RSSI: NSNumber) {
    radio.handleDiscovery(peripheral, advertisementData: advertisementData, rssi: RSSI)
}

```

The `radio` object manages backoff timers, background wake-up triggers, and connection pooling without blocking the delegate callbacks.

## Data Handling and Flow Control

Managing incoming data streams and outgoing write operations requires additional coordination to prevent buffer overflows and ensure complete frame delivery.

### Notification Reassembly

When peripherals send characteristic notifications, the service buffers chunks using `NotificationStreamAssembler` until complete frames arrive. Lines 110-130 of `BLEService+LinkLayerCentralRole.swift` handle this assembly:

```swift
// BLEService+LinkLayerCentralRole.swift#L110-L130
func peripheral(_ peripheral: CBPeripheral, 
                didUpdateValueFor characteristic: CBCharacteristic, 
                error: Error?) {
    guard let data = characteristic.value, !data.isEmpty else { return }
    
    if let frame = bufferNotificationChunk(data, from: peripheral) {
        let packet = BitchatPacket(data: frame)
        emitLinkEvent(.frameDecoded(packet, from: peripheral.identifier))
    }
}

```

Once assembly completes, the service emits a `.frameDecoded` event to the engine, decoupling Bluetooth byte streams from packet processing logic.

### Write Back-Pressure Management

The service manages transmit back-pressure by monitoring the peripheral's readiness signals. When CoreBluetooth invokes `peripheralIsReady(toSendWriteWithoutResponse:)`, the service drains queued writes:

```swift
// BLEService+LinkLayerCentralRole.swift#L80-L87
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
    drainPendingWrites(for: peripheral)
}

func peripheral(_ peripheral: CBPeripheral, 
                didWriteValueFor characteristic: CBCharacteristic, 
                error: Error?) {
    if let error = error {
        SecureLogger.error("Write failed: \(error)")
    } else {
        SecureLogger.debug("Write confirmed")
    }
}

```

Write errors are logged but not retried automatically, maintaining deterministic flow control that prevents infinite retry loops in mesh networks.

## Error Handling and Recovery

The service implements defensive checks at the entry point of every callback to handle panic states gracefully. At lines 44-47, callbacks verify the `isPanicSuspended` flag before processing:

```swift
// BLEService+LinkLayerCentralRole.swift#L44-L47
guard !isPanicSuspended else {
    cleanupLinkState(for: peripheral)
    return
}

```

If a panic reset is in progress, the service aborts the operation and sanitizes the link state to prevent inconsistent state propagation during recovery scenarios.

## Summary

- **Serial Queue Isolation**: All CoreBluetooth callbacks execute on a dedicated `bleQueue` to ensure thread-safe state management without locks.
- **Centralized Delegation**: `BLEService+LinkLayerCentralRole.swift` consolidates both `CBCentralManagerDelegate` and `CBPeripheralDelegate` implementations for unified event handling.
- **State Store Pattern**: `BLELinkStateStore` serves as the single source of truth for link status, updated synchronously during callbacks and accessed read-only by the mesh engine.
- **Event Translation**: Low-level Bluetooth events convert to high-level `LinkEvent` types via `emitLinkEvent(_:)`, processed on the engine's separate `messageQueue`.
- **Radio Abstraction**: `BLERadioController` manages scanning policies and connection back-off, keeping delegate methods focused on protocol handling.
- **Defensive Recovery**: Panic guards in every callback entry point enable graceful degradation during BLE stack resets.

## Frequently Asked Questions

### How does BitChat prevent race conditions in BLE callbacks?

BitChat prevents race conditions by funneling all CoreBluetooth delegate callbacks through a single serial `DispatchQueue` defined in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift). By initializing `CBCentralManager` with this specific queue (lines 71-84), every discovery, connection, and characteristic update executes sequentially, eliminating the need for locks when accessing shared state stores.

### What happens when a BLE peripheral sends fragmented data?

The `BLEService` buffers incoming notification chunks using `NotificationStreamAssembler` within the `peripheral(_:didUpdateValueFor:error:)` callback (lines 110-130). Once the assembler detects a complete frame, it converts the raw bytes into a `BitchatPacket` and emits a `.frameDecoded` event to the mesh engine, ensuring that packet reconstruction happens before cryptographic validation.

### Why does BitChat separate BLE callback processing from mesh engine processing?

The separation maintains responsiveness in the Bluetooth stack while allowing complex mesh operations to proceed without deadline pressure. By translating Bluetooth events into `LinkEvent` types and dispatching them to the engine's `messageQueue`, the `bleQueue` remains available for immediate ACK responses and prevents deadlocks during cryptographic operations that might block for milliseconds.

### How does the service handle Bluetooth power state changes?

The `centralManagerDidUpdateState` callback forwards power transitions to `BLERadioController` (lines 84-100), which implements the policy logic for starting or stopping scans. This abstraction allows the service to react immediately to `.poweredOn` events while the radio controller manages backoff timers and background wake-up sequences independently.