How BitChat's BLE Fragment Assembly Buffer Handles Out-of-Order Packets
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, 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). 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.swiftinstantiates a dedicatedNotificationStreamAssemblerfor each BLE connection.BLELinkStateStore.swiftpersists the assembler instance and its state per link.BLERadioController.swiftreceives raw BLE notification chunks and feeds them into the appropriate assembler'sappend(_:)method.NotificationStreamAssemblerTests.swiftcontains unit tests that verify correct handling of out-of-order, malformed, and fragmented data scenarios.
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.swiftbuffers raw bytes and scans for complete frames after eachappend(_:)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, 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. This class is instantiated per BLE connection in BLEService+LinkLayerCentralRole.swift, with raw notification data fed through BLERadioController.swift and state maintained in BLELinkStateStore.swift.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →