BLEOutboundWriteBuffer: How bitchat Handles BLE Back-Pressure with Prioritized Write Queues

The BLEOutboundWriteBuffer is a per-peripheral write queue in bitchat that enforces back-pressure through byte-capacity trimming, automatically discarding lowest-priority packets when queued data exceeds a configurable limit.

The BLEOutboundWriteBuffer sits at the heart of bitchat's Bluetooth Low Energy (BLE) stack, isolating the hot path of outbound traffic from fragmentation and link-scheduling logic. Implemented in bitchat/Services/BLE/BLEOutboundWriteBuffer.swift, this component ensures that high-priority control frames survive while protecting the BLE stack from memory pressure during heavy file transfers or flaky connections.

How the Buffer Handles Outbound BLE Writes

The buffer maintains a dedicated write queue for each peripheral, keyed by UUID string. When your application calls enqueue(data:for:priority:capBytes:), the buffer performs three critical operations in sequence.

Step 1: Priority-Based Insertion

Each write receives a BLEOutboundWritePriority that combines a priority level (high, fragmentBased, fileTransfer, low) with a monotonic suborder counter. The buffer finds the correct insertion index using firstIndex { item.priority < $0.priority }, ensuring that urgent packets jump ahead of bulk data.

Step 2: Byte-Cap Enforcement and Back-Pressure

After insertion, the buffer sums total queued bytes for that peripheral. While this sum exceeds capBytes, it repeatedly removes items from the tail—the lowest-priority writes—reporting trimmedBytes for each eviction. This capacity-driven trimming is the core back-pressure mechanism.

Step 3: Caller Feedback via EnqueueResult

The method returns an EnqueueResult enum with two cases:

  • .enqueued(trimmed: Int, remaining: Int) — the write survived, possibly after trimming lower-priority items
  • .oversized(Int) — the payload itself exceeded capBytes and was rejected outright

The accompanying accepted Boolean tells you whether your specific write made it into the queue.

Core API and Usage

import Foundation

// Initialize the buffer (normally owned by BLEService)
var writeBuffer = BLEOutboundWriteBuffer()

// Target peripheral
let peripheralID = "A1B2C3-D4E5-F6G7-H8I9-J0K1L2M3N4O5"

// Enqueue a high-priority keep-alive with 1 KB cap
let heartbeat = Data([0x01, 0x02, 0x03])
let result = writeBuffer.enqueue(
    data: heartbeat,
    for: peripheralID,
    priority: .high,
    capBytes: 1024
)

// Handle the result
switch result {
case .enqueued(let trimmed, let remaining):
    if trimmed > 0 {
        print("Trimmed \(trimmed) bytes of lower-priority writes")
    }
    print("Buffer now holds \(remaining) bytes for this peripheral")
case .oversized(let bytes):
    // Payload too large for cap—fragment before retrying
    print("Rejected: \(bytes)-byte payload exceeds \(1024)-byte cap")
}

// When CBPeripheral is ready to write...
let pending = writeBuffer.takeAll(for: peripheralID)
for write in pending {
    // write.payload → CBCharacteristic
}

Lifecycle and Peripheral State Management

The buffer provides three helper methods that BLEService uses during connection churn:

Method Purpose
takeAll(for:) Retrieves and clears all queued writes for a peripheral—called when the link becomes ready to transmit
discardAll(for:) Drops all pending writes without delivery—used on disconnection
prepend(_:for:) Re-inserts writes at queue head—useful when a peripheral reconnects mid-transfer

These operations maintain the invariant that no stale writes accumulate for disconnected peripherals.

Priority Level Semantics

Defined in BLEOutboundWritePriority.swift, the four levels map to real traffic types in bitchat:

  1. .high — Control frames, keep-alives, acknowledgment packets
  2. .fragmentBased — Chunks of an active message being reassembled by peer
  3. .fileTransfer — Bulk payload data where some loss is tolerable
  4. .low — Background sync, non-urgent gossip

When back-pressure strikes, .low items vanish first, followed by .fileTransfer, preserving in-flight fragments and control traffic.

Testing the Back-Pressure Behavior

The test suite in bitchatTests/Services/BLEOutboundWriteBufferTests.swift verifies three invariants:

  • Enqueue respects priority ordering across all insertion sequences
  • Trimming never exceeds capBytes and always targets lowest-priority items
  • The accepted flag and EnqueueResult values are internally consistent

Unit tests run against the buffer in isolation, without requiring actual CBPeripheral instances.

Integration with BLEService

In BLEService.swift, the buffer lives as pendingPeripheralWrites: BLEOutboundWriteBuffer. The writeOrEnqueue(peripheral:data:priority:) method bridges Core Bluetooth's asynchronous readiness callbacks with the buffer's synchronous API. When CBPeripheralManager signals a write completion, the service pulls the next batch via takeAll(for:) and streams it to the characteristic.

This separation—buffer for policy, service for I/O—allows independent testing and tuning of back-pressure parameters without touching BLE stack code.

Summary

  • BLEOutboundWriteBuffer provides per-peripheral, priority-ordered write queuing with automatic back-pressure enforcement
  • Back-pressure works via byte-cap trimming: excess lowest-priority writes are discarded after each enqueue
  • Four priority levels ensure critical control traffic survives congestion
  • EnqueueResult gives explicit feedback on acceptance, trimming, and remaining capacity
  • Lifecycle helpers (takeAll, discardAll, prepend) integrate cleanly with Core Bluetooth connection state

Frequently Asked Questions

What happens if a single payload exceeds the byte cap?

The buffer returns .oversized(bytes) without insertion. The write never enters the queue—no trimming occurs because the payload itself violates the capacity constraint. Fragment large payloads before enqueueing.

How does priority interact with FIFO ordering within the same level?

Items sharing a priority level order by suborder, a monotonic counter incremented on each enqueue. This preserves FIFO semantics among peers while allowing strict precedence across levels.

Can multiple peripherals share buffer capacity?

No. The capBytes parameter applies per-peripheral, not globally. Each peripheral UUID maintains an independent budget, preventing a single chatty peer from starving others.

Where is the back-pressure behavior documented in the codebase?

The architectural rationale appears in docs/ARCHITECTURE_V2.md, which states that pending-write back-pressure now lives in BLEOutboundWriteBuffer as a focused, unit-tested component before deeper fragmentation and link-scheduler work.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →