Bitchat Android Binary Protocol Packet Header: Key Fields and Structure

The Bitchat Android binary protocol packet header is a fixed big-endian structure of either 14 bytes (v1) or 16 bytes (v2) that contains the Version, Type, TTL, Timestamp, Flags, and PayloadLength fields, after which variable sections such as sender ID, recipient ID, route, payload, and signature may follow depending on flag bits.

The permissionlesstech/bitchat-android repository implements a peer-to-peer messaging protocol that remains fully compatible with its iOS counterpart. The Bitchat Android binary protocol packet header sits at the start of every transmitted packet and dictates how the rest of the byte stream is parsed. Its layout, constants, and encode-decode logic are defined in app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt (lines 39–46).

Fixed Header Layout

The fixed header uses big-endian byte order and occupies either 14 bytes for version 1 or 16 bytes for version 2.

Offset Size Field Description
0 1 Version Protocol version (1u or 2u). Determines header size and PayloadLength width.
1 1 Type Message type such as MessageType.MESSAGE or MessageType.FRAGMENT.
2 1 TTL Time-to-live hop count.
3 8 Timestamp Unix epoch milliseconds as an unsigned 64-bit integer (ULong).
11 1 Flags Bit-field controlling optional sections (see below).
12 2 (v1) / 4 (v2) PayloadLength Length of the payload, including compression overhead.

Version

The first byte specifies the protocol version. A value of 1u produces a 14-byte header, while 2u produces a 16-byte header and widens the PayloadLength field to four bytes.

Type

The second byte stores the message type identifier. This field uses the same enumeration values as the iOS implementation so that cross-platform peers can interpret packets consistently.

TTL

The third byte holds the time-to-live counter. Each forwarding node decrements this hop count until the packet expires.

Timestamp

Bytes 3 through 10 encode an unsigned 64-bit Unix timestamp in milliseconds. This ULong value lets peers evaluate message age and order.

Flags

Byte 11 is a bit-field defined in BinaryProtocol.kt that signals which variable sections follow the header:

  • Bit 0 — HAS_RECIPIENT (recipient ID present)
  • Bit 1 — HAS_SIGNATURE (signature present)
  • Bit 2 — IS_COMPRESSED (payload is compressed)
  • Bit 3 — HAS_ROUTE (source-route present; v2 only)

PayloadLength

Starting at byte 12, this field is two bytes wide in v1 and four bytes wide in v2. It stores the length of the payload that follows the header, including any extra bytes prepended when compression is active.

Variable Sections

After the fixed header, the parser reads optional blocks in a deterministic order. Their presence is determined by the flags byte. As implemented in permissionlesstech/bitchat-android, these sections are:

  • Sender ID
  • Recipient ID (if HAS_RECIPIENT is set)
  • Route (if HAS_ROUTE is set; v2+)
  • Payload
  • Signature (if HAS_SIGNATURE is set)

Encoding and Decoding Examples

Encoding a v1 Packet

import com.bitchat.android.protocol.*

val packet = BitchatPacket(
    type = MessageType.MESSAGE.value,
    senderID = hexStringToByteArray("a1b2c3d4e5f60708"),
    timestamp = System.currentTimeMillis().toULong(),
    payload = "Hello, world!".toByteArray(),
    ttl = 5u,                     // 5 hops
    version = 1u                 // v1 → 14‑byte header
)

val binary = packet.toBinaryData()   // uses BinaryProtocol.encode
// `binary` now contains the 14‑byte header followed by sender ID, payload, etc.

Decoding Received Data

val received: ByteArray = ... // from BLE or Tor transport
val decoded = BitchatPacket.fromBinaryData(received)

if (decoded != null) {
    println("Version: ${decoded.version}")
    println("Type: ${decoded.type}")
    println("TTL: ${decoded.ttl}")
    println("Payload (${decoded.payload.size} bytes): ${String(decoded.payload)}")
}

Inspecting Header Bytes Manually

val header = binary.copyOfRange(0, if (binary[0] == 1.toByte()) 14 else 16)

val version = header[0].toUByte()
val type = header[1].toUByte()
val ttl = header[2].toUByte()
val timestamp = ByteBuffer.wrap(header, 3, 8).order(ByteOrder.BIG_ENDIAN).long.toULong()
val flags = header[11].toUByte()
val payloadLen = if (version == 1u.toUByte()) {
    ByteBuffer.wrap(header, 12, 2).order(ByteOrder.BIG_ENDIAN).short.toInt()
} else {
    ByteBuffer.wrap(header, 12, 4).order(ByteOrder.BIG_ENDIAN).int
}

Key Source Files

Summary

  • The Bitchat Android binary protocol packet header is a fixed big-endian structure of 14 bytes (v1) or 16 bytes (v2).
  • It contains six sequential fields: Version, Type, TTL, Timestamp, Flags, and PayloadLength.
  • The Flags byte at offset 11 determines which optional sections—recipient ID, route, payload, or signature—appear after the header.
  • BinaryProtocol.kt in permissionlesstech/bitchat-android is the authoritative source for layout constants and encode/decode routines.

Frequently Asked Questions

What is the total size of the Bitchat Android binary protocol packet header?

The fixed header is 14 bytes for version 1 and 16 bytes for version 2. The size difference comes from the PayloadLength field, which expands from 2 bytes to 4 bytes in v2.

How does the Flags field affect packet parsing?

Byte 11 is a bit-field that tells the parser which optional blocks follow the fixed header. For example, if bit 0 (HAS_RECIPIENT) is set, the parser expects a recipient ID immediately after the sender ID. If bit 3 (HAS_ROUTE) is set and the version is 2 or higher, a source-route block is present before the payload.

What is the difference between v1 and v2 packet headers?

Version 2 extends the PayloadLength field from 2 bytes to 4 bytes and introduces the HAS_ROUTE flag in bit 3. Otherwise, the field order and semantics remain identical to maintain backward compatibility.

Where is the packet header defined in the source code?

The layout, constants, and encode/decode logic are defined in app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt in the permissionlesstech/bitchat-android repository. The BitchatPacket data class declared inside that file (around lines 54–64) holds the header values and payload.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →