# How BitChat's BLE Fragment Assembly Buffer Handles Out-of-Order Packets

> Learn how BitChat's BLE fragment assembly buffer handles out-of-order packets by buffering valid protocol frames and using reset timers, discarding stray bytes to ensure efficient data processing.

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

---

**BitChat's `NotificationStreamAssembler` discards stray or misaligned bytes rather than attempting to reorder them, buffering only correctly sequenced fragments that form valid protocol frames while employing automatic reset timers to prevent indefinite stalls.**

The BLE transport layer in BitChat (permissionlesstech/bitchat) receives data as a stream of raw, potentially fragmented notifications that may arrive out of sequence due to link-layer retransmissions or timing variations. The `NotificationStreamAssembler` class transforms this noisy byte stream into discrete protocol frames using a defensive buffering strategy that prioritizes data integrity over complex reassembly logic.

## Buffer Architecture and Frame Detection

In [`NotificationStreamAssembler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NotificationStreamAssembler.swift), the class maintains a mutable `Data` buffer initialized as `private var buffer = Data()` to accumulate every incoming BLE notification chunk. The `append(_:)` method implements an incremental parsing loop that repeatedly attempts to **consume complete frames** from the front of the buffer after each insertion.

### Header Validation

A valid frame must begin with protocol version `1` or `2`. The assembler checks `buffer.count >= minimumFramePrefix` and validates the version byte before proceeding. When the version is valid, `BinaryProtocol.headerSize(for:)` computes the expected header size and total frame length. If the buffer contains insufficient bytes for a complete frame, the parsing loop exits and waits for additional data to arrive.

## Discarding Out-of-Order and Malformed Data

Rather than implementing complex reordering logic, the buffer employs an aggressive **drop-first** strategy to maintain alignment.

### Stray Byte Removal

If the leading byte is not a valid protocol version, the assembler immediately executes `dropped.append(buffer.removeFirst())` and continues scanning (see lines 61-64 in [`NotificationStreamAssembler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NotificationStreamAssembler.swift)). This process repeats until a valid header aligns at the buffer front. Consequently, any out-of-order packet or garbage data that arrives where a frame header is expected is **safely discarded** rather than buffered for later reordering.

### Padding Detection

BLE link-layer padding bytes are detected and removed via `discardLeadingPaddingIfPresent()`. If present, the entire padding block is stripped from the buffer before frame parsing begins, ensuring that transport-level artifacts do not corrupt protocol boundary detection.

## Stall Recovery and Overflow Protection

The assembler includes defensive mechanisms to prevent resource exhaustion from incomplete or lost fragments.

### Timeout-Based Reset

When a partial frame is detected (`buffer.count < frameLength`), the assembler records `pendingFrameStartedAt` and `pendingFrameExpectedLength`. If the missing fragments do not arrive within `TransportConfig.bleAssemblerStallResetMs` milliseconds, the code invokes `resetState()` and sets `didReset = true` (checked at lines 33-38). This **automatic reset** clears the incomplete data and prevents the connection from stalling indefinitely due to lost out-of-order fragments.

### Hard Cap Enforcement

Should the buffer grow beyond `TransportConfig.bleNotificationAssemblerHardCapBytes`, the assembler logs an overflow error and calls `resetState()` to flush the entire buffer. This hard cap prevents memory exhaustion from sustained out-of-order or malicious traffic.

## Integration with BLE Services

The fragment assembly pipeline spans multiple source files:

- **`BLEService+LinkLayerCentralRole.swift`** instantiates a dedicated `NotificationStreamAssembler` for each BLE connection.
- **[`BLELinkStateStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkStateStore.swift)** persists the assembler instance and its state per link.
- **[`BLERadioController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLERadioController.swift)** receives raw BLE notification chunks and feeds them into the appropriate assembler's `append(_:)` method.
- **[`NotificationStreamAssemblerTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NotificationStreamAssemblerTests.swift)** contains unit tests that verify correct handling of out-of-order, malformed, and fragmented data scenarios.

```swift
import BitChat

var assembler = NotificationStreamAssembler()

// Simulate fragmented BLE data (potentially out-of-order)
let chunkA = Data([0x01, 0x00, 0x00, 0x05])          // Valid frame header
let chunkB = Data([0xFF, 0xFF])                      // Out-of-order garbage
let chunkC = Data([0x48, 0x65, 0x6C, 0x6C, 0x6F])   // Payload completing frame

// Feed chunks as they arrive
let res1 = assembler.append(chunkA)   // Returns no frames (incomplete)
let res2 = assembler.append(chunkB)   // Drops [0xFF, 0xFF], reports in res2.dropped
let res3 = assembler.append(chunkC)   // Returns assembled frame

print("Frames assembled:", res3.frames.count)  // 1
print("Dropped bytes:", res2.dropped)            // [255, 255]

```

## Summary

- **Incremental parsing** in [`NotificationStreamAssembler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NotificationStreamAssembler.swift) buffers raw bytes and scans for complete frames after each `append(_:)` operation.
- **Out-of-order packets** are handled by strict validation: any byte that does not start a valid frame is immediately discarded via `buffer.removeFirst()`.
- **Stall protection** resets the buffer state if partial frames remain incomplete beyond `bleAssemblerStallResetMs`, preventing hung connections.
- **Overflow protection** flushes the buffer entirely if it exceeds `bleNotificationAssemblerHardCapBytes`.
- Only **in-order, complete frames** are emitted to downstream components, ensuring protocol integrity without complex reordering logic.

## Frequently Asked Questions

### Does BitChat reorder out-of-order BLE packets?

No. According to the BitChat source code in [`NotificationStreamAssembler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NotificationStreamAssembler.swift), the assembler does not implement reordering logic. Instead, it drops any bytes that cannot form a valid frame header, ensuring only correctly aligned, in-sequence fragments are processed.

### What happens if a BLE fragment is lost?

If the expected remaining bytes of a partial frame do not arrive within `TransportConfig.bleAssemblerStallResetMs` milliseconds, the assembler triggers `resetState()` and sets `didReset = true`, discarding the incomplete data and allowing the connection to recover.

### How does the buffer handle BLE padding bytes?

The assembler calls `discardLeadingPaddingIfPresent()` before attempting to parse frame headers. If padding is detected, the entire padding block is stripped from the buffer front, ensuring alignment-sensitive parsing proceeds correctly.

### Where is the fragment assembly logic implemented?

The core logic resides in [`bitchat/Services/NotificationStreamAssembler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NotificationStreamAssembler.swift). This class is instantiated per BLE connection in `BLEService+LinkLayerCentralRole.swift`, with raw notification data fed through [`BLERadioController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLERadioController.swift) and state maintained in [`BLELinkStateStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLELinkStateStore.swift).