# Understanding the BitChat Mesh Packet Structure: Complete Wire Format Guide

> Explore the BitChat mesh packet structure. Learn the complete wire format, including the fixed-size header and variable sections, defined in the permissionlesstech/bitchat repository.

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

---

**A BitChat mesh packet is a fixed-size header followed by optional variable sections controlled by flag bits, defined by the `BitchatPacket` Swift struct and encoded via the `BinaryProtocol` in the permissionlesstech/bitchat repository.**

The BitChat project implements a peer-to-peer mesh networking protocol where every message traversing the network is wrapped in a `BitchatPacket` structure. According to the source code in `localPackages/BitFoundation/Sources/BitFoundation/`, packets use a compact binary format optimized for transport over BLE and other low-bandwidth mesh transports. This guide breaks down the exact structure of a BitChat mesh packet, explaining every header byte, variable section, and flag bit as implemented in the actual source.

## Overview of the BitChat Mesh Packet Format

A BitChat mesh packet follows a simple, layered arrangement: a **fixed-size header** followed by **optional variable sections** whose presence is signaled by flag bits in the header. The wire format supports two protocol versions — version 1 and version 2 — with the primary difference being payload length sizing and the addition of a routing section in version 2.

The logical fields are defined in [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift), while the binary serialization and deserialization logic lives in [`BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift). Together, these files implement the complete structure that every node in the mesh expects to parse upon delivery.

## Fixed-Size Packet Header

The header is the first portion of any BitChat mesh packet and carries the metadata needed for routing, timestamping, and flag decoding. Its size depends on the protocol version: **14 bytes for version 1** and **16 bytes for version 2**.

| Offset | Size | Field | Meaning |
|--------|------|-------|---------|
| 0 | 1 B | **Version** | Protocol version (`1` or `2`) |
| 1 | 1 B | **Type** | application-specific message type |
| 2 | 1 B | **TTL** | Hop limit; decremented at each relay |
| 3–10 | 8 B | **Timestamp** | Unix-epoch time, big-endian |
| 11 | 1 B | **Flags** | Bitfield indicating optional sections |
| 12–13 | 2 B | **PayloadLength (v1)** | Length of payload or compressed payload |
| 12–15 | 4 B | **PayloadLength (v2)** | Same as above but 32-bit for larger packets |

> See the exact header layout in the `headerDescription` section of [`BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift) at line 23–30 of the source.

The `Flags` byte is the most important control element. It determines whether the following variable sections appear in the packet. The relevant flag bits are `hasRecipient`, `hasSignature`, `isCompressed`, `hasRoute`, and `isRSR`.

## Optional Variable Sections Controlled by Flags

After the header, the packet contains sections whose presence depends on the flag bitfield. The sections appear in a fixed order, so the decoder can step through them deterministically once flags are parsed.

| Section | Size (bytes) | Presence Condition |
|---------|--------------|--------------------|
| **SenderID** | 8 B | Always present (origin node identifier) |
| **RecipientID** | 8 B | Present if `hasRecipient` flag is set |
| **Route** | 1 B + N × 8 B | Present if `hasRoute` flag is set (v2+ only); first byte is hop count, each hop is an 8-byte node ID |
| **Payload** | variable | Always present; may be compressed if `isCompressed` is set |
| **Original Payload Size** | 2 B (v1) or 4 B (v2) | Included only when the payload is compressed |
| **Signature** | 64 B | Present if `hasSignature` flag is set |

Notably, `SenderID` and the `Payload` are never optional in practice — every packet must carry a source identifier and a message body. The other sections are only added when needed for directed routing, transport compression, or cryptographic authenticity. The flags are recalculated during encoding based on which optional properties are actually populated on the Swift struct.

## Overall Packet Layout

Putting the header and the optional sections together produces this simplified wire layout:

```

[Version][Type][TTL][Timestamp][Flags][PayloadLength]
[SenderID] [RecipientID?] [Route?] [Payload] [OrigSize?] [Signature?]

```

All multi-byte integer fields are encoded using **big-endian network byte order**. This ensures cross-platform decode regardless of CPU architecture. The `Timestamp` is the Unix epoch time represented as a `UInt64`; the route hop list is read as an array of 8-byte node ID values.

## Swift Representation: The `BitchatPacket` Struct

The logical model backing this structure is the `BitchatPacket` Swift struct, defined in [`localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift) at lines 11–27.

```swift
public struct BitchatPacket: Codable {
    public let version: UInt8          // 1 or 2
    public let type: UInt8
    public let senderID: Data          // 8 bytes
    public let recipientID: Data?      // 8 bytes if present
    public let timestamp: UInt64
    public let payload: Data
    public var signature: Data?        // 64 bytes if signed
    public var ttl: UInt8
    public var route: [Data]?          // array of 8-byte hops (v2+ only)
    public var isRSR: Bool             // Reliable-store-and-forward flag
}

```

The optional properties map directly to the optional wire sections: if a given optional property is set on the struct, the corresponding flag bit is flipped and the data is written to the wire. Well, the route is only written in version 2; in version 1 the `hasRoute`` flag is ignored even if the property is populated.

Note the `isRSR` flag, which stands for **Reliable Store and Forward** — this is a distinctive BitChat feature that indicates the packet deserves special delayed delivery handling in the mesh, distinct from other flags.

## Encoding and Decoding with `BinaryProtocol`

The actual binary translation is handled by the `BinaryProtocol` class in [`localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift):

- `BinaryProtocol.encode(_:)` takes a `BitchatPacket` and produces the binary wire format complete with compression, route sanitization, and flag calculation from the optional fields present.
- `BinaryProtocol.decode(_:)` reads the same buffer back into a `BitchatPacket`, performing length validation, safety checks, and optional decompression.

During encoding, the routine also enforces protocol rules: for version 1 packets, the route array is discarded even if present; for version 2, route section is emitted only when `hasRoute` is set. Compression is applied by the payload if flagged.

## Practical Code Examples

The following examples demonstrate how to create, serialize, and decode a BitChat mesh packet, using convenience methods defined in [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift).

**Creating a packet and serializing it:**

```swift
import Foundation
import BitFoundation   // contains BitchatPacket & BinaryProtocol

let sender = Data([0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08])
let payload = "Hello, mesh!".data(using: .utf8)!

let packet = BitchatPacket(
    type: 0x10,                // custom message type
    senderID: sender,
    recipientID: nil,          // broadcast
    timestamp: UInt64(Date().timeIntervalSince1970),
    payload: payload,
    signature: nil,
    ttl: 5,
    version: 2,                // allow route field
    route: [Data([0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA])],
    isRSR: false
)

if let wireData = packet.toBinaryData() {
    // `wireData` is ready to be transmitted over BLE or other mesh transports
    print("Encoded packet, \(wireData.count) bytes")
}

```

**Decoding a received packet:**

```swift
if let receivedPacket = BitchatPacket.from(wireData) {
    print("Version:", receivedPacket.version)
    print("Sender:", receivedPacket.senderID.hexString)
    if let recipient = receivedPacket.recipientID {
        print("Recipient:", recipient.hexString)
    }
    print("Payload:", String(data: receivedPacket.payload, encoding: .utf8) ?? "<binary>")
}

```

Both code snippets rely on the convenience methods `packet.toBinaryData()` and `BitchatPacket.from(_:)` which wrap the `BinaryProtocol` encoder and decoder internally.

## Key Source Files

| File | Purpose |
|------|---------|
| [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift) | Swift model describing the logical fields of a mesh packet |
| [`BigProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BigProtocol.swift) | Implements the binary wire format (header layout, flag handling, compression, route encoding) |
| [`BinaryProtocolTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocolTests.swift) | Unit tests exercising encoding/decoding, routing, compression, and version differences |
| [`BitchatPacketTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacketTests.swift) | Tests constructing packets with optional fields and verifying round-trip integrity |

These source files together define, implement, and validate the complete structure of a BitChat mesh packet. Referencing them gives you the authoritative details beyond this guide.

## Summary

- A BitChat mesh packet uses a **fixed-size header** of 14 bytes (v1) or 16 bytes (v2) followed by **optional variable sections** gated by flag bits.  
- The **`BitchatPacket` Swift struct** in [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatPacket.swift) models the logical fields, while **`BinaryProtocol`** handles the binary encoding and decoding.  
- Version 2 adds a bigger 4-byte payload length and an optional **route hop list**, and the `isRSR` flag indicates reliable store-and-forward semantics.  
- All multi-byte fields use **big-endian (network) byte order**, making the format portable across architectures.

## Frequently Asked Questions

### How large is the BitChat mesh packet header?

The header is **14 bytes for version 1** and **16 bytes for version 2**, accounting for the widened payload-length field. It contains version, type, TTL, timestamp, flags, and the payload length value.

### What is the `isRSR` flag used for?

The `isRSR` flag marks a packet as requiring the mesh to perform **reliable store-and-forward handling**, which means that the intermediate nodes may store the packet and deliver it later if the recipient is offline at the moment of arrival. This is a designed for delayed, guaranteed-style message delivery.

### When is a compression field present in the wire format?

The **Original Payload Size** field is present only when the `isCompressed` flag is set. When compression is used, the payload is the compressed data, and the original-size field tells the decoder the expected uncompressed length. The size itself is 2 bytes in v1 and 4 bytes in v2.

### Does a BitChat packet always include a signature?

No, a signature is optional. The 64-byte signature section appears only when the packet has the `hasSignature` flag set, which is the case for authenticated messages. If no cryptographic proof is provided, the signature bytes are omitted to keep short message formats compact.

### Where is the routing hop list stored in the packet?

The route list is stored as a variable-length section that comes after the recipient field. It starts with a 1-byte hop count, followed by the hop node IDs (each 8 bytes). Only version 2 packs the route section at all, and only when the `hasRoute` flag is set by the sender.