# How the BLE Link Layer in BitChat Interacts with CoreBluetooth: Architecture Guide

> Explore how BitChat's BLE Link Layer integrates with CoreBluetooth. Learn about its architecture and dual role as central and peripheral for seamless data transfer.

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

---

**BitChat's `BLEService` class implements both `CBCentralManagerDelegate` and `CBPeripheralManagerDelegate` to simultaneously act as central and peripheral, marshaling every raw CoreBluetooth event into a thread-safe link-state model and forwarding it to the app via `TransportEvent`.**

BitChat is an open-source peer-to-peer chat application that implements a custom Bluetooth Low Energy link layer directly on top of Apple's CoreBluetooth framework. Understanding how the BLE link layer in BitChat interacts with CoreBluetooth is essential for developers building offline-first mesh messaging apps on iOS. This guide breaks down the architecture, thread-safety model, and event flow using the actual source from the `permissionlesstech/bitchat` repository.

## Architecture Overview

The BLE stack in BitChat is organized into three logical layers that sit between CoreBluetooth and the app's UI.

- **Core-Bluetooth Bridge**: Encapsulated in `BLEService`, this layer owns `CBCentralManager` and `CBPeripheralManager` and receives all raw delegate callbacks such as `didDiscover`, `didConnect`, and `didUpdateValue`.
- **Link-State Store**: Normalizes each physical connection into a `BLELink` object tracked by `BLELinkStateStore`, `BLELinkAuthState`, and `BLELinkBindings`. All mutations occur on the dedicated serial `bleQueue`.
- **Transport-Level Interface**: Exposes high-level events including `TransportEvent.bluetoothStateUpdated` and peer-snapshot changes to consumers like `ChatViewModel` through the `Transport` protocol.

## CoreBluetooth to BLE Link Layer Flow

### Initialization and Queue Setup

In [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift), the initializer creates a dedicated serial dispatch queue named `bleQueue`. When `initializeBluetoothManagers` is `true`, it instantiates both `CBCentralManager` and `CBPeripheralManager` on that queue (see lines 26-34). Running CoreBluetooth on a private queue prevents delegate callbacks from stalling the main thread.

### Scanning and Advertising

The `BLERadioController` object drives radio behavior. It calls `centralManager?.scanForPeripherals` to discover peers and `peripheralManager?.startAdvertising` to announce the local device. Scanning results arrive through the delegate method `centralManager(_:didDiscover:advertisementData:rssi:)`.

### Connection Lifecycle

When a remote peripheral is discovered, `BLEService` initiates a connection via `centralManager?.connect`. Successful connections trigger `centralManager(_:didConnect:)`, while disconnections or errors trigger `centralManager(_:didDisconnectPeripheral:error:)`. Each event updates the internal connection graph maintained by the link-state store.

### Data Transfer and Fragmentation

Peripheral write requests enter the link layer through `peripheralManager(_:didReceiveWrite:)`. The service buffers incoming `CBATTRequest` objects in `BLEInboundWriteBuffer` and eventually hands them to `BLEFragmentHandler` for reassembly. Because BLE MTU is limited, outbound packets are split by `BLEOutboundFragmentTransferScheduler` and written using `CBPeripheralManager.updateValue` or `CBPeripheral.writeValue`.

### Bluetooth State Propagation

All `CBManagerState` changes, including `centralManagerDidUpdateState` and `peripheralManagerDidUpdateState`, are converted into `TransportEvent.bluetoothStateUpdated(state)` (see lines 82-84 of [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift)). Downstream observers use this event to react to powered-on, powered-off, or unauthorized states without touching CoreBluetooth directly.

## Thread Safety and the Link-State Store

### BLELinkStateStore and Queue Isolation

`BLELinkStateStore` is the single source of truth for physical link state. It is accessed exclusively from the serial `bleQueue`, and synchronous reads are performed inside a `bleQueue.sync` block via `readLinkState`. This design guarantees that `BLELink` identity, connection status, and advertising state never race against UI threads or the engine's `messageQueue`.

### Authentication and Binding Security

`BLELinkAuthState` tracks whether a link has been signature-verified, gating encrypted payload acceptance. `BLELinkBindings` ties an active Noise session to a specific physical `BLELink`, preventing replay attacks where a forged announce could bind an absent peer ID to a stale connection. According to source comments in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) (lines 22-31), both structures follow an engine-owned contract—mutated only on the serial `messageQueue` and read by the link layer under `bleQueue` protection—that enforces the option-B isolation model.

## Transport Event Propagation to the UI

`BLEService` does not expose CoreBluetooth objects to `ChatViewModel` or other services. Instead, it translates every significant event into a `TransportEvent` value. The `Transport` protocol defines `eventDelegate` of type `TransportEventDelegate`, which receives method calls such as `didReceiveTransportEvent(_:)`.

When Bluetooth is disabled, the emitted `.bluetoothStateUpdated(let state)` event allows the view model to toggle a SwiftUI alert by checking `state != .poweredOn`. This indirection keeps UI code free of `CoreBluetooth` imports.

## Practical Code Examples

### Initializing the BLE Service

```swift
let bleService = BLEService(
    keychain: KeychainManager.shared,
    idBridge: NostrIdentityBridge(),
    identityManager: SecureIdentityStateManager.shared,
    initializeBluetoothManagers: true
)

bleService.startServices()

```

Calling `startServices()` schedules an initial announce after a short delay (see lines 65-73 of [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift)).

### Sending a Public Broadcast Message

```swift
bleService.sendMessage(
    "Hello, mesh!",
    mentions: [],
    to: nil,
    messageID: UUID().uuidString,
    timestamp: Date()
)

```

This builds a `BitchatPacket`, signs it with the Noise service, records a deduplication ID, and calls `broadcastPacket(_:)` to write the data to every connected link (see lines 86-92 of [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift)).

### Handling an Incoming Peripheral Write

```swift
func peripheralManager(_ peripheral: CBPeripheralManager,
                       didReceiveWrite requests: [CBATTRequest]) {
    pendingWriteBuffers.append(contentsOf: requests)
}

```

The delegate method—implemented later in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift)—stores raw bytes in `BLEInboundWriteBuffer`. Later, `BLEFragmentHandler` running on the `messageQueue` reassembles complete packets and emits `TransportEvent.messageReceived`.

### Observing Bluetooth State in a SwiftUI View Model

```swift
class ChatViewModel: ObservableObject {
    @Published var showBluetoothAlert = false

    init(transport: Transport) {
        transport.eventDelegate = self
    }
}

extension ChatViewModel: TransportEventDelegate {
    func didReceiveTransportEvent(_ event: TransportEvent) {
        if case .bluetoothStateUpdated(let state) = event {
            showBluetoothAlert = (state != .poweredOn)
        }
    }
}

```

`ChatViewModel` receives state updates through `receiveTransportEvent(_:)` in [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift) and updates the UI accordingly.

## Key Source Files

- [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) — Core BLE implementation, central and peripheral managers, and message processing.
- [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift) — `Transport`, `TransportEvent`, and the bridge that forwards BLE events to higher-level app layers.
- [`bitchat/Services/BLE/LinkState/BLELinkStateStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/LinkState/BLELinkStateStore.swift) — Normalizes physical connections into `BLELink` objects and tracks connection state.
- [`bitchat/Services/BLE/Auth/BLELinkAuthState.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/Auth/BLELinkAuthState.swift) — Holds per-link authentication flags such as signature-verified status.
- [`bitchat/Services/BLE/Bindings/BLELinkBindings.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/Bindings/BLELinkBindings.swift) — Maps Noise sessions to physical links to prevent replay attacks.
- [`bitchat/Services/BLE/Fragmentation/BLEFragmentHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/Fragmentation/BLEFragmentHandler.swift) — Reassembles packets that exceed the BLE MTU.
- [`bitchat/Services/BLE/IO/BLEIncomingWriteBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/IO/BLEIncomingWriteBuffer.swift) — Buffers incoming `CBATTRequest` objects until fragments are complete.
- [`bitchat/Services/BLE/Radio/BLERadioController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/Radio/BLERadioController.swift) — Drives scanning, advertising, duty-cycle management, and exposes the link-state store to the engine.

## Summary

- `BLEService` wraps CoreBluetooth by implementing both `CBCentralManagerDelegate` and `CBPeripheralManagerDelegate` inside a single service class.
- A dedicated serial `bleQueue` isolates CoreBluetooth callbacks and link-state mutations from the UI and engine queues.
- `BLELinkStateStore`, `BLELinkAuthState`, and `BLELinkBindings` provide a normalized, thread-safe model of every physical link.
- Large messages are transparently fragmented and reassembled using `BLEFragmentHandler` and `BLEInboundWriteBuffer`.
- The rest of the application consumes BLE changes exclusively through `TransportEvent`, decoupling the chat logic from CoreBluetooth internals.

## Frequently Asked Questions

### What is the role of BLEService in BitChat's CoreBluetooth stack?

`BLEService` is the sole bridge between CoreBluetooth and the application. It instantiates `CBCentralManager` and `CBPeripheralManager` on a private `bleQueue`, handles every delegate callback, and translates raw Bluetooth events into structured link-state updates that the engine and UI can consume safely.

### How does BitChat ensure thread safety between CoreBluetooth and the UI?

All CoreBluetooth initialization and `BLELinkStateStore` mutations occur on a serial dispatch queue called `bleQueue`. The UI and engine read link state through `bleQueue.sync` blocks, and higher-level code receives asynchronous `TransportEvent` updates. This prevents race conditions without blocking the main thread.

### What happens when a BLE message exceeds the standard MTU?

BitChat fragments large packets using `BLEFragmentHandler`. Incoming bytes from `peripheralManager(_:didReceiveWrite:)` are buffered in `BLEInboundWriteBuffer` until the full fragment set arrives. Outbound data is scheduled by `BLEOutboundFragmentTransferScheduler` and transmitted in chunks via `CBPeripheral.writeValue` or `CBPeripheralManager.updateValue`.

### How does the app detect when Bluetooth is powered off or unavailable?

State changes in CoreBluetooth trigger `centralManagerDidUpdateState` or `peripheralManagerDidUpdateState` inside `BLEService`. The service immediately emits `TransportEvent.bluetoothStateUpdated(state)` (see [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift)). `ChatViewModel` observes this event and presents a SwiftUI alert whenever the state is not `.poweredOn`.