# BitchatProtocol Binary Packet Format and Wire Encoding Specification

> Explore the BitchatProtocol binary packet format and wire encoding. Understand the compact header and big-endian integer encoding for efficient network communication.

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

---

**The Bitchat protocol employs a compact binary envelope defined in [`BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift) that consists of a fixed 14‑byte (v1) or 16‑byte (v2) header followed by optional variable‑length sections, encoding all multi‑byte integers in network byte order (big‑endian).**

The `permissionlesstech/bitchat` repository implements a lightweight mesh messaging protocol optimized for Bluetooth Low Energy (BLE) transports. At its core, the `BitchatProtocol` defines a binary wire format that balances compactness with extensibility, allowing devices to exchange routed, signed, and compressed messages while staying within typical BLE MTU constraints.

## Fixed Header Structure

Every packet begins with a fixed-size header that determines the packet version and structure. The header is **14 bytes** for protocol version 1 (v1) and **16 bytes** for version 2 (v2), with the latter expanding the payload length field from 2 to 4 bytes.

| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| 0 | 1 byte | **Version** | Protocol version: `1` or `2`. |
| 1 | 1 byte | **Type** | Application-defined message type. |
| 2 | 1 byte | **TTL** | Hop-limit for mesh routing. |
| 3–10 | 8 bytes | **Timestamp** | Unix epoch in seconds as UInt64. |
| 11 | 1 byte | **Flags** | Bitfield indicating optional sections. |
| 12–13 (v1)<br>12–15 (v2) | 2 or 4 bytes | **PayloadLength** | Size of payload plus any compression pre‑amble. |

All numeric values are stored in **network byte order** (big-endian) to ensure cross-platform compatibility. This layout is defined in [`localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift) (lines 23–30), with the encoding logic implemented in the `encode` method (lines 81–108).

## Flag Bits and Optional Fields

Byte 11 of the header contains a bitfield that signals which optional fields follow the header. The flags are evaluated during encoding (lines 89–96) and decoding (lines 115–122) of [`BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift).

| Bit | Name | Meaning |
|-----|------|---------|
| 0 | `hasRecipient` | An 8‑byte recipient ID follows the sender ID. |
| 1 | `hasSignature` | A 64‑byte Ed25519 signature follows the payload. |
| 2 | `isCompressed` | Payload is zlib-compressed and preceded by original size. |
| 3 | `hasRoute` | Routing hop list present (v2 only). |
| 4 | `isRSR` | Resend‑Request flag (reserved for future use). |
| 5–7 | Reserved | Currently unused. |

## Variable-Length Payload Sections

After the header, the packet contains ordered variable sections based on the flag bits. The `decodeCore` method (lines 70–141) reads these sections sequentially, validating sizes before allocation.

1. **SenderID** – 8 bytes (always present).
2. **RecipientID** – 8 bytes if the `hasRecipient` flag is set.
3. **Route** (v2 only) – If `hasRoute` is set:
   - 1 byte hop count (`N`)
   - `N × 8` bytes of hop IDs (each truncated or padded to 8 bytes)
4. **Compression Pre‑amble** – If `isCompressed` is set:
   - 2 bytes (v1) or 4 bytes (v2) storing the original uncompressed size
5. **Payload** – Raw or compressed data (`payloadLength` bytes, excluding pre‑amble).
6. **Signature** – 64 bytes if `hasSignature` is set.

## Compression and Padding

The protocol automatically compresses payloads exceeding 256 bytes using **zlib** when `CompressionUtil.shouldCompress` returns true. When compression is active, the encoder stores the original size in the pre‑amble (handled at lines 36–49) so the decoder can verify expansion ratios and allocate buffers safely (lines 60–78).

After encoding, `MessagePadding.pad` appends alignment bytes to optimize for BLE transmission. You can disable padding by passing `padding: false` to the encode method, as invoked at the end of the `encode` function (lines 48–52).

## Encoding and Decoding in Swift

The following Swift example demonstrates creating a v2 packet with routing and signature, encoding it to binary, and decoding it on the receiving side.

```swift
import Foundation
import BitFoundation

// Build a v2 packet with recipient, route, and signature
let sender = Data([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
let recipient = Data([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11])
let payload = "Hello, Bitchat!".data(using: .utf8)!
let signature = Data(repeating: 0x9F, count: 64)
let route = [
    Data([0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17]),
    Data([0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27])
]

let packet = BitchatPacket(
    type: 0x01,
    senderID: sender,
    recipientID: recipient,
    timestamp: UInt64(Date().timeIntervalSince1970),
    payload: payload,
    signature: signature,
    ttl: 5,
    version: 2,
    route: route,
    isRSR: false
)

// Encode to binary (with default padding)
guard let binary = packet.toBinaryData() else {
    fatalError("Encoding failed")
}

// Decode on receiver side
guard let received = BitchatPacket.from(binary) else {
    fatalError("Decoding failed")
}

print("Type:", received.type)
print("Payload:", String(data: received.payload, encoding: .utf8)!)

```

The `toBinaryData()` method invokes `BinaryProtocol.encode`, which constructs the header, sets flag bits, optionally compresses the payload, serializes the route list, and applies padding. The `BitchatPacket.from(_:)` initializer calls `BinaryProtocol.decode` to reverse the process.

## Protocol Limits and Constraints

The binary format enforces strict size boundaries to accommodate BLE transports, as documented in [`BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift) (lines 58–62):

- **Maximum packet size:** 65,535 bytes (limited by 16‑bit length field)
- **Minimum packet size:** 21 bytes (header plus sender ID)
- **Typical BLE packet:** Optimized to fit within 512 bytes

## Summary

- **BitchatProtocol** uses a fixed 14‑byte (v1) or 16‑byte (v2) header followed by optional sections toggled via bitflags.
- All multi‑byte integers use **network byte order** (big-endian).
- Optional fields include recipient ID (8 bytes), 64‑byte signatures, zlib-compressed payloads with size pre‑ambles, and v2 routing tables.
- Automatic padding aligns packets for BLE transmission via `MessagePadding.pad`.
- Implementation resides in [`localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift).

## Frequently Asked Questions

### What byte order does BitchatProtocol use for multi‑byte fields?

All multi‑byte numbers in the Bitchat binary packet format are encoded in **network byte order** (big-endian). This ensures consistent interpretation across different processor architectures and is explicitly handled in the `encode` and `decodeCore` methods of [`BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift).

### How does the protocol handle large payloads?

When a payload exceeds 256 bytes, `CompressionUtil.shouldCompress` evaluates whether to apply zlib compression. If compressed, the original size is stored in a 2‑byte (v1) or 4‑byte (v2) pre‑amble before the compressed data, allowing the decoder to validate the expansion ratio and allocate the correct buffer size.

### What is the maximum size of a Bitchat packet?

The protocol supports packets up to **65,535 bytes** due to the 16‑bit payload length field in v1. However, the implementation optimizes for BLE networks where packets typically remain under 512 bytes. The minimum valid packet size is 21 bytes, comprising the header and mandatory 8‑byte sender ID.

### How is mesh routing information encoded?

In protocol version 2, routing information is appended when the `hasRoute` flag (bit 3) is set. The route section consists of a 1‑byte hop count followed by that many 8‑byte hop identifiers. This structure allows intermediate nodes to append their IDs as the message traverses the mesh without altering the fixed header.