BitChat Mesh Protocol Binary Packet Format: Structure and Implementation
The BitChat mesh protocol uses a compact, versioned binary wire format defined in BinaryProtocol.swift, where packets are represented by the BitchatPacket struct and serialized through static encode/decode methods, supporting optional compression, cryptographic signatures, and source routing across v1 and v2 specifications.
The BitChat mesh network, maintained in the permissionlesstech/bitchat repository, implements a lightweight communication layer optimized for Bluetooth Low Energy (BLE) constraints. Understanding the BitChat mesh protocol binary packet format is essential for developers building compatible clients or analyzing network traffic. The format prioritizes minimal overhead while remaining extensible through a flags-based variable header system.
Binary Packet Structure Overview
The binary format defines a fixed header followed by optional variable-length fields. According to the implementation in BinaryProtocol.swift, the header occupies 14 bytes for v1 packets and 16 bytes for v2 packets, with the specific layout documented in lines 23-30 of the source file.
Header Components
Every packet begins with a standardized header containing routing and metadata information:
- Version (1 byte): Protocol version identifier, currently
1or2 - Type (1 byte): Application-level message type (e.g., chat messages, file transfers)
- TTL (1 byte): Hop-limit for mesh routing; decremented on each relay to prevent infinite loops
- Timestamp (8 bytes): Unix epoch time in seconds, encoded as big-endian unsigned 64-bit integer
- Flags (1 byte): Bit-field indicating optional sections:
0x01– Recipient ID present0x02– Signature present (Ed25519)0x04– Payload compressed0x08– Route present (v2+ only)0x10– RSR (Return Source Route) flag
- Payload Length (2 bytes in v1, 4 bytes in v2): Size of the payload including optional original-size field when compressed
Variable-Length Fields
Following the header, the parser reads optional fields based on the flags byte, as implemented in lines 52-57 of BinaryProtocol.swift:
- Sender ID (8 bytes): Fixed-size identifier for the packet origin, padded or truncated as needed
- Recipient ID (8 bytes, optional): Target recipient identifier; omitted for broadcast messages when
hasRecipientflag is unset - Route (variable, v2+): Source-routing information consisting of a 1-byte hop count followed by
count × 8bytes of hop identifiers - Original Size (2 bytes v1 / 4 bytes v2, conditional): Uncompressed payload size present when compression flag is set
- Payload (variable): Raw or compressed message data
- Signature (64 bytes, optional): Ed25519 cryptographic signature present when
hasSignatureflag is set - Padding (variable): Zero-padding to nearest block size for BLE-MTU alignment, handled by
MessagePaddingpost-serialization
Protocol Versions and Feature Differences
The BitChat mesh protocol maintains backward compatibility while extending capabilities through versioned specifications.
Version 1 Specification
Version 1 provides the foundational mesh communication format with specific constraints:
- Fixed 14-byte header with 16-bit payload length field
- Maximum packet size of 65,535 bytes, limited by the 16-bit length encoding
- No route support; packets rely on flood-based or table-driven routing without source-route embedding
- Compression support with 2-byte original size fields
Version 2 Enhancements
Version 2 expands the protocol to support larger payloads and advanced routing, as detailed in lines 99-115 of BinaryProtocol.swift:
- Expanded 16-byte header with 32-bit payload length field, enabling packets exceeding 65KB
- Source routing capabilities via the route field (flag
0x08), allowing explicit hop-by-hop path specification - 32-bit original size fields for compression metadata
- Support for up to 255 explicit routing hops in the source-route list
Encoding and Decoding Implementation
The BinaryProtocol class in BinaryProtocol.swift provides static methods for serialization and deserialization, interacting with the BitchatPacket struct defined in BitchatPacket.swift.
Serialization Flow
The encoding process follows a structured pipeline:
- Header Assembly: Write version, type, TTL, timestamp, and calculate initial flags
- Variable Field Writing: Append sender ID, conditionally write recipient ID and route based on flags
- Compression Evaluation: If payload exceeds 256 bytes,
CompressionUtil.shouldCompressdetermines whether to compress; if compressed, setisCompressedflag and prepend original size - Payload Appending: Write compressed or raw payload data
- Signature Injection: Append 64-byte Ed25519 signature if
hasSignatureflag is set - Padding Application: Apply
MessagePaddingto align to BLE-MTU block sizes
import Foundation
import BitFoundation
let senderID = Data([0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08])
let recipientID = Data([0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10])
let payload = "Hello, mesh!".data(using: .utf8)!
let packet = BitchatPacket(
type: 0x01,
senderID: senderID,
recipientID: recipientID,
timestamp: UInt64(Date().timeIntervalSince1970),
payload: payload,
signature: nil,
ttl: 8,
version: 2,
route: nil,
isRSR: false
)
guard let binary = BinaryProtocol.encode(packet) else {
fatalError("Encoding failed")
}
print("Encoded packet size:", binary.count)
Deserialization Flow
Decoding reverses the encoding process with validation:
- Header Parsing: Extract version, type, TTL, timestamp, flags, and payload length from the fixed header
- Flag Evaluation: Determine which optional fields exist based on the flags byte
- Field Extraction: Read sender ID, conditional recipient ID, and route (if v2+ with route flag)
- Decompression: If
isCompressedflag is set, read original size and decompress payload using the inverse ofCompressionUtil - Signature Verification: Validate 64-byte Ed25519 signature if present
- Object Construction: Instantiate
BitchatPacketwith extracted values
if let decoded = BinaryProtocol.decode(binary) {
print("Version:", decoded.version)
print("Sender:", decoded.senderID.hexString)
print("Payload:", String(data: decoded.payload, encoding: .utf8) ?? "<binary>")
}
Optional Features and Extensions
The binary format supports several optional capabilities controlled through the flags byte, enabling efficient bandwidth utilization and security.
Payload Compression
When payload data exceeds 256 bytes, the encoder evaluates compression via CompressionUtil.shouldCompress. If compression reduces size, the encoder:
- Sets the
0x04compression flag - Prepends the uncompressed original size (2 bytes for v1, 4 bytes for v2) before the compressed payload
- Automatically decompresses during decoding when the flag is detected, as implemented in lines 36-48 and 60-71 of
BinaryProtocol.swift
Message Padding for BLE MTU
To accommodate Bluetooth Low Energy maximum transmission unit constraints, the protocol supports optional padding via MessagePadding. This adds zero-padding after serialization to align packets to specific block sizes, ensuring efficient radio utilization without fragmenting frames.
Practical Implementation Examples
Adding Source Routes (Version 2)
For directed routing through specific mesh nodes, v2 packets support explicit route embedding:
let hop1 = Data([0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA])
let hop2 = Data([0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB])
var routedPacket = packet
routedPacket.route = [hop1, hop2] // Up to 255 hops supported
routedPacket.version = 2 // Required for route support
let routedBinary = BinaryProtocol.encode(routedPacket)!
Working with Signed Packets
To create authenticated packets, populate the signature field with a 64-byte Ed25519 signature and set the hasSignature flag (0x02):
var signedPacket = packet
signedPacket.signature = ed25519SignatureData // 64 bytes
// BinaryProtocol.encode automatically sets flag 0x02 when signature is non-nil
let signedBinary = BinaryProtocol.encode(signedPacket)
Summary
- The BitChat mesh protocol binary packet format uses a fixed 14-byte (v1) or 16-byte (v2) header with variable-length optional fields controlled by a flags byte.
- Version 2 introduces 32-bit payload lengths and source routing capabilities not available in Version 1.
- Compression automatically activates for payloads exceeding 256 bytes, with original size metadata prepended to the payload.
- Cryptographic signatures use Ed25519 (64 bytes) and are conditionally included based on the flags field.
- All encoding/decoding logic resides in
BinaryProtocol.swift, while packet structure is defined inBitchatPacket.swiftwithin theBitFoundationlocal package.
Frequently Asked Questions
What is the maximum payload size supported by the BitChat binary format?
Version 1 supports payloads up to 65,535 bytes due to its 16-bit length field, while Version 2 expands this to approximately 4 GB using a 32-bit length field. However, practical limits are constrained by BLE-MTU sizes and mesh network bandwidth, with compression recommended for payloads exceeding 256 bytes.
How does the protocol handle packet routing in mesh networks?
The protocol supports two routing modes: broadcast (omitting recipient ID) and unicast (including 8-byte recipient ID). Version 2 adds explicit source routing through an optional route field containing up to 255 hop identifiers, enabling predetermined path traversal through specific mesh nodes.
Where is the binary packet format defined in the source code?
The wire format is implemented in localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift, which provides static encode and decode methods. The data model is defined in BitchatPacket.swift within the same directory, while unit tests covering version handling, compression, and padding reside in BinaryProtocolTests.swift and BinaryProtocolPaddingTests.swift.
What compression algorithm does BitChat use for large payloads?
The source code references CompressionUtil.shouldCompress for determining compression eligibility, with the isCompressed flag (0x04) indicating compressed payloads. The implementation automatically handles compression for payloads exceeding 256 bytes and stores the original uncompressed size before the payload data to facilitate verification during decompression.
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 →