# How BitChat Handles Large Message Fragmentation Over BLE

> Discover how BitChat manages large message fragmentation over BLE. Learn about its indexed fragments, three-tier pipeline, and built-in recovery mechanisms for efficient data transfer.

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

---

**BitChat splits payloads exceeding the BLE MTU into indexed fragments and reassembles them using a three-tier pipeline consisting of `BLEFragmentHeader`, `BLEFragmentAssemblyBuffer`, and `BLEFragmentHandler`, with built-in memory caps, oversized detection, and stalled-broadcast recovery.**

When a message exceeds Bluetooth Low Energy's Maximum Transmission Unit (approximately 512 bytes), the BitChat protocol must fragment the payload across multiple packets and reliably reconstruct it on the receiving end. The `permissionlesstech/bitchat` repository implements a robust reassembly system that handles out-of-order delivery, memory constraints, and broadcast-specific synchronization. Understanding how this large message fragmentation over BLE works reveals the architectural decisions behind reliable mesh communication.

## The Architecture Behind BLE Fragment Reassembly

The reassembly pipeline relies on three tightly integrated components that parse, buffer, and orchestrate fragments.

### BLEFragmentHeader

The **`BLEFragmentHeader`** struct, defined in [`bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFragmentAssemblyBuffer.swift) (lines 9-53), parses metadata from an incoming `BitchatPacket`. It validates that the raw packet contains at least **13 bytes**, then extracts:

- An 8-byte **fragment ID** uniquely identifying the message stream
- The **fragment index** and **total fragment count**
- The original **sender ID** and **packet type**
- A flag indicating whether this is a **broadcast fragment**

If the packet fails minimum size validation or does not represent a fragment, the header initialization returns `nil`, allowing the handler to process it as a standard packet.

### BLEFragmentAssemblyBuffer

The **`BLEFragmentAssemblyBuffer`** class (lines 56-135 in [`BLEFragmentAssemblyBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentAssemblyBuffer.swift)) manages transient storage for in-progress assemblies. It indexes fragments using a composite **`BLEFragmentKey`** combining the sender ID and fragment ID.

Key safety mechanisms include:

- **`maxInFlightAssemblies`**: A configurable cap on concurrent assemblies. When the limit is exceeded, the buffer evicts the oldest entry (LRU policy).
- **`assemblyLimit(for:)`**: Per-packet-type size limits that trigger eviction if the projected total exceeds the threshold.
- **Timestamp tracking**: Each assembly records the "last fragment" arrival time to enable stalled-broadcast detection.

The buffer's `append(_:)` method returns one of three enumeration cases:
- **`.stored`**: The fragment was accepted and the assembly remains incomplete.
- **`.complete`**: All fragments arrived; the method returns the concatenated data.
- **`.oversized`**: The projected total exceeds the safety limit; the assembly is discarded.

### BLEFragmentHandler

The **`BLEFragmentHandler`** in [`bitchat/Services/BLE/BLEFragmentHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFragmentHandler.swift) (lines 24-76) serves as the entry point for all BLE traffic. Its `handle(_:from:)` method orchestrates the reassembly flow by:

1. Discarding **self-originated fragments** to prevent re-assembling data after app relaunch
2. Tracking broadcast fragments via **`trackPacketSeen`** for gossip-sync rounds
3. Forwarding headers to the assembly buffer
4. Re-injecting completed packets into the standard receive pipeline after zeroing the TTL

## The Fragment Reassembly Flow

When a large message arrives, BitChat processes it through a deterministic seven-stage pipeline:

1. **Packet Receipt**: The BLE service delivers each `BitchatPacket` to `BLEFragmentHandler.handle(_:from:)`.

2. **Header Extraction**: The handler attempts `BLEFragmentHeader(packet:)` initialization. If the packet lacks fragment metadata, processing returns early.

3. **Self-Fragment Suppression**: If the sender ID matches the local peer ID, the fragment is tracked (if broadcast) but not assembled, preventing redundant processing of the device's own transmissions.

4. **Gossip Sync Tracking**: Broadcast fragments are recorded via `trackPacketSeen` so that subsequent gossip-sync rounds can identify missing pieces.

5. **Buffer Appending**: The handler calls `environment.appendFragment(header)`, which invokes `BLEFragmentAssemblyBuffer.append`. This method creates a new assembly entry via `startAssemblyIfNeeded` (evicting the oldest if at capacity), checks the projected size against `assemblyLimit(for:)`, and stores the fragment data at the specified index. When the stored count equals the advertised total, it concatenates fragments in order and returns `.complete`.

6. **Logging**: `logFragmentAssemblyResult` generates debug output for each state transition (started, stored, completed, oversized).

7. **Re-injection**: Upon receiving a `.complete` result, the handler decodes the raw bytes using `BinaryProtocol.decode`, validates the inner sender via `isAcceptedIngressPayload`, zeros the TTL, and pushes the reassembled packet through `processReassembledPacket`.

## Memory Management and Safety Limits

BitChat enforces strict memory boundaries during reassembly to prevent denial-of-service via maliciously large or numerous fragment streams.

When `BLEFragmentAssemblyBuffer.append` detects a new assembly key, it checks the current count against **`maxInFlightAssemblies`**. If the cap is reached, it evicts the oldest incomplete assembly before allocating storage. Each assembly’s projected total size is validated against **`assemblyLimit(for:)`** based on the original packet type. Assemblies exceeding this limit are immediately purged and reported as `.oversized`, freeing the associated memory.

## Recovering Stalled Broadcasts

Broadcast packets present unique challenges in mesh networks where fragments may be lost or delayed. BitChat implements stalled-broadcast detection via **`stalledBroadcastFragmentIDs`** in the assembly buffer.

Called periodically, this method identifies assemblies that have received no new fragments for **`stalledAfter`** seconds. It respects a **`retryAfter`** window to avoid spamming recovery requests. The method returns fragment IDs (encoded big-endian) that should be targeted by a `REQUEST_SYNC` packet, enabling the mesh to recover missing fragments without requiring full retransmission of the original large message.

## Implementation Example

The following Swift demonstrates fragment reception and reassembly:

```swift
// 1️⃣ Create a fragment header from a raw BLE packet
if let header = BLEFragmentHeader(packet: incomingPacket) {
    // 2️⃣ Append it to the assembly buffer (max 8 concurrent assemblies)
    let result = buffer.append(header, maxInFlightAssemblies: 8)

    // 3️⃣ React to the result
    switch result {
    case .stored(let hdr, let started):
        print("Stored fragment \(hdr.index+1)/\(hdr.total), started: \(started)")
    case .complete(let hdr, let data, _):
        print("Reassembled packet of \(data.count) bytes for id \(hdr.idLogString)")
        // Decode & process the full packet…
    case .oversized(let hdr, let projected, let limit, _):
        print("Dropping oversized assembly (projected \(projected) > limit \(limit))")
    }
}

```

To detect and recover stalled broadcasts:

```swift
// 4️⃣ Detect stalled broadcast fragments (called periodically)
let stalledIDs = buffer.stalledBroadcastFragmentIDs(
    stalledAfter: 5,      // seconds without new fragment
    retryAfter: 10)       // seconds before re‑requesting
// Encode these IDs into a REQUEST_SYNC packet to recover missing fragments.

```

## Summary

- **BLEFragmentHeader** parses fragment metadata from raw packets in [`BLEFragmentAssemblyBuffer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFragmentAssemblyBuffer.swift).
- **BLEFragmentAssemblyBuffer** provides LRU eviction, per-type size limits, and stalled-broadcast tracking via `BLEFragmentKey` indexing.
- **BLEFragmentHandler** orchestrates the flow, filters self-fragments, and re-injects completed packets after TTL zeroing.
- The three-tier architecture handles large message fragmentation over BLE while preventing memory exhaustion through configurable caps and oversized detection.
- Stalled broadcast recovery uses timestamp tracking to request missing fragments via `REQUEST_SYNC` packets.

## Frequently Asked Questions

### What triggers message fragmentation in BitChat?

BitChat automatically fragments messages when the payload exceeds the BLE MTU of approximately 512 bytes. The protocol splits the data into indexed chunks, each wrapped in a `BitchatPacket` with a `BLEFragmentHeader` containing sequence numbers and total fragment count.

### How does BitChat prevent memory exhaustion during fragment reassembly?

The **`BLEFragmentAssemblyBuffer`** enforces a configurable **`maxInFlightAssemblies`** limit, evicting the oldest incomplete assembly when the cap is reached. Additionally, **`assemblyLimit(for:)`** checks projected total sizes against per-type safety limits, immediately discarding any assembly that would exceed its allocated memory threshold.

### How does BitChat handle fragments received out of order?

Fragments are stored in a dictionary keyed by their index within the assembly buffer. The buffer waits until the count of stored fragments equals the advertised total before concatenating them in sequential order. This design tolerates arbitrary arrival sequences as long as all fragments arrive within the stalled-broadcast timeout window.

### What is the purpose of zeroing the TTL on reassembled packets?

When **`BLEFragmentHandler`** completes reassembly, it zeros the Time-To-Live (TTL) before re-injecting the packet into the receive pipeline. This prevents the reassembled message from being re-transmitted indefinitely through the mesh, ensuring that only the original sender's TTL policy governs propagation scope.