# How BitChat Outbound Packet Policy and Write Buffering Work: A Deep Dive into the BLE Stack

> Discover how BitChat's outbound packet policy and write buffering optimize BLE communication. Learn about link planner, ATT limits, and priority queues for reliable control traffic.

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

---

**BitChat uses a two-layer outbound system where `BLEOutboundLinkPlanner` determines packet fragmentation based on per-link ATT limits, and `BLEOutboundWriteBuffer` manages priority-ordered, byte-capped queues to ensure critical control traffic is never dropped.**

The **BitChat** messenger routes messages over Bluetooth Low Energy (BLE) in a decentralized, permissionless mesh. Sending data reliably over BLE requires handling small MTUs, variable link quality, and competing traffic types—from urgent Noise handshake frames to multi-megabyte file transfers. The repository `permissionlesstech/bitchat` solves this with an **outbound packet policy** that decides how to split payloads, and a **write buffering** system that schedules those fragments without exhausting memory or starving high-priority traffic.

---

## Outbound Packet Policy: Link-Aware Fragmentation Planning

The packet policy answers three questions for every outbound message: *what* is the maximum fragment size, *how many* fragments are needed, and *what priority* should the scheduler assign. This logic lives in two planner types and a set of policy constants.

### Link Planning with `BLEOutboundLinkPlanner`

When BitChat discovers a BLE peripheral, it queries the GATT layer for ATT limits:

- `peripheralWriteLimits` — the maximum bytes the peripheral can receive in a single **write request** or **write command**.
- `centralNotifyLimits` — the maximum bytes the central (this device) can receive in a **notification**.

`BLEOutboundLinkPlanner.plan(...)` selects the smallest viable limit and returns a `BLEOutboundLinkPlan` containing:

| Field | Purpose |
|-------|---------|
| `fragmentChunkSize` | The byte size of each fragment, computed by `BLEOutboundPacketPolicy.fragmentChunkSize(forLinkLimit:)` |
| `packetPriority` | A `BLEOutboundWritePriority` value encoding urgency |

This plan becomes the contract for all fragmentation that follows.

### Fragment Planning with `BLEOutboundFragmentPlanner`

Given a payload and a link plan, `BLEOutboundFragmentPlanner.makePlan(...)` produces a `BLEOutboundFragmentPlan`. This struct contains an array of `BLEOutboundFragmentTransferRequest` objects, each sized to fit within the `fragmentChunkSize`. The planner also annotates each fragment with priority metadata derived from the original message type.

---

## Write Buffering: Priority Queues with Byte Caps

Once fragments are planned, they enter `BLEOutboundWriteBuffer`, the central staging area before CoreBluetooth transmission. The buffer is structurally simple—`[UUID: [BLEPendingWrite]]`—but its eviction policy is carefully tuned for mesh networking.

### Priority Levels in `BLEOutboundWritePriority`

Priority is a comparable struct with two fields: `level` (primary sort) and `suborder` (tie-breaker). The four standard levels are:

- **`high`** (level 0) — Noise handshake and other control frames. Never dropped if avoidable.
- **`fragment(totalFragments:)`** (level 1) — Mesh message fragments. Higher fragment counts get slightly higher suborder to discourage head-of-line blocking for large messages.
- **`fileTransfer`** (level 2, suborder `Int.max - 1`) — File chunks. Throttled but not starved.
- **`low`** (level 2, suborder `Int.max`) — Background traffic. First to be trimmed under pressure.

### Enqueueing and Automatic Trimming

The `enqueue(data:for:priority:capBytes:)` method in [`BLEOutboundWriteBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundWriteBuffer.swift) inserts writes in sorted order, then enforces the per-link cap:

```swift
var total = queue.reduce(0) { $0 + $1.data.count }
while total > capBytes && !queue.isEmpty {
    let removed = queue.removeLast()          // drop lowest-priority items
    trimmedBytes += removed.data.count
    total -= removed.data.count
}

```

The returned `EnqueueResult` reports whether the new write survived and how many bytes were discarded. This guarantees that a burst of file-transfer data cannot evict a pending Noise handshake frame for the same peripheral.

### Taking and Discarding Writes

When CoreBluetooth signals that a peripheral is ready to accept data, the stack calls:

```swift
let pendingWrites = buffer.takeAll(for: peripheralID)

```

This drains the entire queue for that peripheral in priority order. If the peripheral disconnects, `discardAll(for:)` removes the entry immediately to free memory.

### Prepending for Retransmission

In rare cases—typically after a failed write with response—the stack may need to retry fragments ahead of newer traffic. `prepend(_:for:)` inserts writes at the front while maintaining priority ordering among the prepended items.

---

## Practical Usage: Sending a Fragmented Mesh Message

The following Swift snippet demonstrates the full flow from link discovery to buffer enqueueing:

```swift
import Bitchat

let peripheralID = "A1:B2:C3:D4:E5:F6"
let maxWrite = 120          // discovered ATT write limit
let maxNotify = 90          // discovered notification limit

// Build link plan from discovered limits
let linkPlan = BLEOutboundLinkPlanner.plan(
    peripheralWriteLimits: [maxWrite],
    centralNotifyLimits: [maxNotify]
)

// Prepare a 350-byte mesh payload
let payload = Data(repeating: 0xAB, count: 350)

// Fragment according to plan
let fragmentPlan = try BLEOutboundFragmentPlanner.makePlan(
    request: BLEOutboundFragmentTransferRequest(
        payload: payload,
        linkPlan: linkPlan,
        peripheralID: peripheralID
    )
)

// Enqueue fragments with priority-based buffering
var buffer = BLEOutboundWriteBuffer()
for fragment in fragmentPlan.fragments {
    let priority = BLEOutboundWritePriority.fragment(
        totalFragments: fragmentPlan.fragments.count
    )
    let result = buffer.enqueue(
        data: fragment,
        for: peripheralID,
        priority: priority,
        capBytes: 1024           // per-link memory budget
    )
    // Check result.trimmedBytes if you need to log evictions
}

```

When CoreBluetooth invokes the delegate method indicating space is available, drain the buffer:

```swift
let readyToSend = buffer.takeAll(for: peripheralID)
for write in readyToSend {
    peripheral.writeValue(
        write.data,
        for: characteristic,
        type: .withResponse   // or .withoutResponse per policy
    )
}

```

---

## Key Source Files

| File | Responsibility |
|------|--------------|
| [`BLEOutboundLinkPlanner.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundLinkPlanner.swift) | Translates ATT limits into actionable link plans |
| [`BLEOutboundFragmentPlanner.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundFragmentPlanner.swift) | Splits payloads into size-constrained fragments |
| [`BLEOutboundWriteBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundWriteBuffer.swift) | Priority queue with byte-cap enforcement and trimming |
| [`BLEOutboundFragmentTransferScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundFragmentTransferScheduler.swift) | Orchestrates planner → buffer → CoreBluetooth flow |
| [`BLEOutboundWriteBufferTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEOutboundWriteBufferTests.swift) | Validates ordering, cap behavior, and eviction logic |

All files are located under `bitchat/Services/BLE/` in the repository.

---

## Summary

- **Outbound packet policy** in BitChat dynamically selects fragment sizes based on per-link ATT write and notify limits discovered at connection time.
- `BLEOutboundLinkPlanner` and `BLEOutboundFragmentPlanner` separate the concerns of limit detection and payload splitting, producing `BLEOutboundFragmentTransferRequest` objects ready for transmission.
- **Write buffering** via `BLEOutboundWriteBuffer` maintains per-link priority queues with configurable byte caps, automatically discarding lowest-priority traffic when memory pressure exceeds the limit.
- The four priority levels—`high`, `fragment`, `fileTransfer`, and `low`—ensure that critical control frames are never starved by bulk data transfers.
- The buffer's `takeAll`, `discardAll`, and `prepend` APIs integrate cleanly with CoreBluetooth's asynchronous write callbacks and disconnection events.

---

## Frequently Asked Questions

### How does BitChat prevent large file transfers from blocking urgent control messages?

The `BLEOutboundWriteBuffer` uses **priority-ordered eviction**. When the per-link byte cap is exceeded, it removes items from the tail of the queue—always lowest-priority first. Since control frames use `BLEOutboundWritePriority.high` (level 0) and file transfers use `fileTransfer` (level 2), critical traffic is protected even during bulk uploads.

### What happens to buffered data when a BLE peripheral disconnects?

The stack calls `buffer.discardAll(for: peripheralID)`, which removes the entire queue entry for that peripheral. This prevents memory leaks in transient mesh topologies where peers may appear and disappear frequently.

### Can BitChat retransmit failed fragments ahead of newer traffic?

Yes. The `prepend(_:for:)` method in `BLEOutboundWriteBuffer` allows the stack to insert retry fragments at the head of the queue. These prepended items maintain their internal priority ordering, ensuring that a retransmitted high-priority fragment still precedes a new low-priority packet.

### Where is the maximum ATT write size detected in the codebase?

Peripheral capabilities are discovered during GATT service exploration and passed to `BLEOutboundLinkPlanner.plan(peripheralWriteLimits:centralNotifyLimits:)`. The smallest applicable limit drives the `fragmentChunkSize` returned in the link plan, ensuring compliance with both local and remote constraints.