# How BitChat's P2P Network Layers Are Implemented: A Deep Dive Into the BLE Mesh Stack

> Explore how BitChat implements its P2P network layers with a four-layer stack. Discover BLE mesh, Nostr/Tor fallbacks, and deterministic simulation in this deep dive.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-01

---

**BitChat implements its P2P network as a four-layer stack separating BLE radio handling, protocol state, feature modules, and transport abstractions, with deterministic simulation support and Nostr/Tor fallback bridges.**

BitChat's peer-to-peer networking architecture follows a strictly layered design that enables testability, cross-platform portability, and graceful degradation from BLE mesh to internet bridges. This guide examines each layer's responsibilities, concurrency contracts, and implementation details based on the permissionlesstech/bitchat source code.

## The Four-Layer P2P Network Stack

BitChat's P2P network layers are organized from physical radio up to application boundary:

| Layer | Responsibility | Core Abstraction |
|-------|---------------|------------------|
| BLE Link Layer | Bluetooth Low-Energy radio operations | `BLELinkLayer` / `SimulatedLinkLayer` |
| Mesh Engine | Protocol state, encryption, routing | Sans-I/O core with `handle(event) -> [Effect]` |
| Feature Modules | Functional subsystems (chat, voice, groups) | Self-contained modules with private state |
| App Boundary / Transport | Public façade for UI and external systems | `Transport` protocol with capability discovery |

## BLE Link Layer: Isolated Radio Handling

The **BLE Link Layer** in [`BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/BLE-ARCHITECTURE-V3.md) owns all direct CoreBluetooth interaction. This is the *only* layer that imports `CoreBluetooth`, ensuring clean separation from higher-level logic.

### Key Responsibilities

- Advertising and scanning scheduling
- Connection management and MTU negotiation
- Back-pressure buffering for link writes
- Emitting `LinkEvent` (link up/down, bytes received, writable state)
- Consuming `LinkCommand` (send bytes, start/stop scanning)

The layer runs on a dedicated `bleQueue` serial queue. All link-state mutations are confined to this queue, preventing race conditions with the protocol engine.

### Simulated Link Layer for Testing

For deterministic testing without hardware, `SimulatedLinkLayer` implements the same interface. This enables multi-node property-based tests in [`SimulatedMeshTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SimulatedMeshTests.swift) that verify protocol behavior across simulated topologies.

```swift
// The link layer emits events consumed by the engine
protocol LinkLayer {
    var events: AsyncStream<LinkEvent> { get }
    func execute(_ command: LinkCommand) async
}

// Real implementation uses CoreBluetooth
final class BLELinkLayer: LinkLayer { /* ... */ }

// Simulated implementation for testing
final class SimulatedLinkLayer: LinkLayer { /* ... */ }

```

## Mesh Engine: Sans-I/O Protocol Core

The **Mesh Engine** contains BitChat's core protocol implementation in **BitFoundation**. It is intentionally *sans-I/O*—pure logic that transforms events into effects, making it hardware-agnostic and trivially testable.

### Architecture Principles

All mutable state lives in the engine, accessed exclusively through a single-writer `engine` serial queue. Cross-thread access uses `onEngine` scheduling primitives. This design appears in:

- [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift) — packet encapsulation and fragmentation
- [`NoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSession.swift) and [`NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSessionManager.swift) — Noise XX handshake and encryption
- [`BitchatMessage.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatMessage.swift) — message model with mesh channel routing
- [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift) — peer identifiers with mesh prefix (`mesh:`)

### Effect-Based Core Loop

The engine's central function has this signature:

```swift
func handle(event: EngineEvent) -> [Effect]

```

Effects describe actual operations—send packet, update peer registry, notify feature modules—while the core remains pure and synchronous.

### Post-Migration Link-Auth Binding

After architectural migration, the engine owns `BLELinkAuthState` and `BLELinkBindings`. This guarantees atomic "check-binding + act" semantics when mapping physical links to peer identities.

## Feature Modules: Extensible Functional Blocks

**Feature Modules** implement independent subsystems without modifying engine core. Each module:

- Owns its private state store
- Registers for specific message types via `MessageType`
- Lives in sources like [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift), voice handling in [`MessageType.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageType.swift), or [`MeshDiagnostics.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshDiagnostics.swift)

Adding functionality requires creating a new module, not touching protocol internals. This extends to courier routing, private media, groups, verification, and diagnostics.

## App Boundary and Transport Abstraction

The **Transport** layer provides the public façade used by UI components. Defined in [`Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Transport.swift), it exposes a minimal protocol that concrete implementations satisfy:

```swift
// Core transport used throughout the UI
protocol Transport {
    func send(_ message: BitchatMessage) async throws
    var isMeshReachable: Bool { get }
    // ...
}

```

Capability protocols discovered via `as?` casting extend this:

- `MeshBridgingTransport` — Nostr bridge fallback
- `PanicResettingTransport` — hard reset capability
- `BluetoothStateReporting` — radio state inspection

The `AppRuntime` and feature models inject concrete transport implementations based on runtime conditions.

## Concurrency Contracts Across Layers

BitChat's P2P network layers enforce three ownership domains to prevent deadlocks:

1. **`engine`** — engine queue, single-writer for protocol state
2. **`bleQueue`** — BLE operations, isolated from engine
3. **Lock-backed stores** — e.g., `BLEPeerRegistryStore` for peer metadata

No domain synchronously waits on another. Only asynchronous hand-offs are permitted. This contract is validated in [`BLEQueueContractTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEQueueContractTests.swift) (see §55-71 of [`BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/BLE-ARCHITECTURE-V3.md)).

## Fallback: Nostr and Tor Bridging

When BLE mesh cannot reach a peer, BitChat bridges traffic via **Nostr**—a decentralized pub-sub network—with optional **Tor** for censorship resistance.

The `MeshBridgingTransport` capability signs payloads with a per-cell Nostr identity and publishes to geohash-based rendezvous topics. Wire format details are documented in [`SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/SOURCE_ROUTING.md) and [`PEER-ID-ROTATION.md`](https://github.com/permissionlesstech/bitchat/blob/main/PEER-ID-ROTATION.md).

```swift
// Check mesh reachability before bridging
if !transport.isMeshReachable {
    // Casts to bridging capability if available
    if let bridge = transport as? MeshBridgingTransport {
        await bridge.send(message)  // Signs with Nostr key, publishes
    }
}

```

## Practical Usage Examples

Creating and sending mesh messages follows this pattern:

```swift
// 1. Create mesh-prefixed peer identifier
let meshPeer = PeerID(str: "mesh:\(UUID().uuidString)")
// PeerID.swift: https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift

// 2. Build message for mesh channel
let msg = BitchatMessage(
    id: .init(),
    sender: myPeerID,
    channelID: .mesh,
    timestamp: Date(),
    type: .chat,
    payload: Data("Hello mesh!".utf8)
)
// BitchatMessage.swift: https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BitchatMessage.swift

// 3. Send through transport façade (encrypts with Noise, fragments, enqueues)
await transport.send(msg)
// Transport.swift: https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Transport.swift

```

## Key Source Files by Layer

| Layer | Primary Files |
|-------|---------------|
| BLE Link Layer | [`BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/BLE-ARCHITECTURE-V3.md), `BLEService+LinkLayerCentralRole.swift`, `BLEService+LinkLayerPeripheralRole.swift` |
| Mesh Engine | [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift), [`NoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSession.swift), [`NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSessionManager.swift), [`BitchatMessage.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatMessage.swift), [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift), [`PeerCapabilities.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerCapabilities.swift) |
| Feature Modules | [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift), [`MessageType.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageType.swift) (voice), [`MeshDiagnostics.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshDiagnostics.swift) |
| Transport / UI | [`Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Transport.swift), [`MeshBridgingTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshBridgingTransport.swift), `AppRuntime` |
| Simulation / Tests | [`SimulatedMesh.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SimulatedMesh.swift), [`SimulatedMeshTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SimulatedMeshTests.swift), [`BLEQueueContractTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEQueueContractTests.swift) |

## Summary

- **Four explicit layers** separate BLE radio, protocol logic, features, and transport abstractions
- **Sans-I/O engine core** enables hardware-free testing via `SimulatedLinkLayer`
- **Three-domain concurrency contract** (`engine`, `bleQueue`, lock stores) guarantees deadlock-free operation
- **Capability-based transport design** allows runtime discovery of Nostr bridging, panic reset, and Bluetooth state reporting
- **Nostr/Tor fallback** extends reachability when BLE mesh is insufficient

## Frequently Asked Questions

### What is the sans-I/O pattern used in BitChat's mesh engine?

The sans-I/O design means the engine core contains no actual network or hardware operations. Instead, it pure functions transform `EngineEvent` inputs into `Effect` outputs describing what should happen. Real I/O occurs at layer boundaries. This allows the entire protocol state machine to run in deterministic tests with `SimulatedMesh`, validating complex multi-peer interactions without physical devices.

### How does BitChat prevent deadlocks between Bluetooth and protocol threads?

BitChat enforces three strict ownership domains: the `engine` serial queue for protocol state, the `bleQueue` serial queue for CoreBluetooth operations, and lock-protected stores for registry data. No domain may synchronously block on another—only async hand-offs are permitted. This architecture is documented in §55-71 of [`BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/BLE-ARCHITECTURE-V3.md) and validated by [`BLEQueueContractTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEQueueContractTests.swift).

### Can BitChat's P2P network operate without Bluetooth hardware?

Yes. The `SimulatedLinkLayer` implements the same `LinkLayer` protocol as the real BLE implementation, feeding fabricated events into the engine. This enables [`SimulatedMeshTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SimulatedMeshTests.swift) to run multi-node mesh scenarios deterministically, verifying packet routing, deduplication, and relay policy without any CoreBluetooth dependency.

### How does message routing work when a peer is outside BLE range?

The `MeshBridgingTransport` capability provides fallback transport over Nostr, a decentralized pub-sub network. When `transport.isMeshReachable` returns false, the UI can cast to `MeshBridgingTransport` and call `send()`, which signs payloads with a per-cell Nostr identity and publishes to geohash-based topics. Optional Tor tunneling adds censorship resistance for internet-bridged traffic.