‹ All AirLink docs

AirLink

Wire format

Byte-level reference for the mesh packet, the sealed message, the inner content types, call signalling, voice frames, and the Bluetooth and Bonjour identifiers.

Conventions

  • Integers in the packet header, the sealed message and voice frames are big-endian.
  • A mesh packet is self-framing: the same bytes travel over Bluetooth notifications, Bluetooth writes and TCP, chunked to each link's size and reassembled by the receiver.

Mesh packet

A 41-byte header followed by the payload.

OffsetSizeFieldTypeMeaning
02magicbytesA1 7E. Lets a receiver resynchronise after a lost chunk
21versionu81
31kindu81 direct, 2 receipt, 3 hello, 4 voice
41ttlu8Hops of life left. Clamped to 8 when the packet is created
516idbytesRandom message identifier. The only key for duplicate suppression
218recipientbytesRecipient fingerprint. FF FF FF FF FF FF FF FF means everyone
298timestampu64Sender's clock at creation, seconds since 1970
374lengthu32Payload length, at most 8,192
41lengthpayloadbytesDepends on the kind

Decoding fails on fewer than 41 bytes, a wrong magic, a version other than 1, an unknown kind, a length over 8,192, or a payload shorter than its declared length. The length is checked before any payload is copied.

Kinds

KindValuePayloadHop limit at originQueued
direct1Sealed message8, 6 or 5 by densityYes, in the sender's outbox
receipt2Sealed message carrying the receipt8, 6 or 5 by densityNo
hello3Contact card as JSON, not encrypted2No
voice4Sealed voice frame1No

Contact card

The hello payload, and the form in which contacts are stored, is a JSON object:

{
  "signingKey": "<32-byte Ed25519 public key, base64>",
  "agreementKey": "<32-byte X25519 public key, base64>",
  "displayName": "Ama"
}

A card is valid when both keys are 32 bytes.

Fingerprint and number

fingerprint = SHA-256(signingKey || agreementKey)[0..8]
number      = Crockford base 32 of fingerprint[0..5], grouped 4-4

The alphabet is 0123456789ABCDEFGHJKMNPQRSTVWXYZ. When parsing, input is uppercased, O becomes 0, I and L become 1, and hyphens and spaces are ignored. ABCD-1234, ABCD-0000 and WXYZ-1234 are reserved and never issued.

Sealed message

The payload of a direct or receipt packet.

payload = encapsulatedKey (32) || HPKE ciphertext (inner || 16-byte tag)
ParameterValue
SuiteHPKE, DHKEM X25519, HKDF-SHA256, ChaCha20-Poly1305
ModeBase (one-shot, sender unauthenticated at the HPKE layer)
Infoairlink-mesh-hpke-v1
Associated dataThe packet's 16-byte message identifier

Inner plaintext

OffsetSizeField
032Sender's Ed25519 public key
3232Sender's X25519 public key
6464Ed25519 signature over messageID || plaintext
128Padded plaintext

The padded plaintext is a 4-byte length followed by the plaintext and zeros up to the next bucket of 256, 512, 1,024, 2,048, 4,096 or 8,192 bytes. The signature covers the unpadded plaintext.

A sealed message therefore adds 176 bytes to its padded plaintext: 32 for the encapsulated key, 128 for the sender's keys and signature, and 16 for the tag. Because the whole payload must fit 8,192 bytes, the largest bucket that can be delivered is 4,096.

Content types

The first byte of the plaintext says what the message carries.

ValueContentBody
1TextUTF-8 text
2Ranging tokenUltra-wideband discovery token for precision finding
3Sound requestReserved. Defined in the core; the apps neither send it nor act on it
4ReceiptThe 16-byte identifier of the message being acknowledged
5Call invite<service> <nonce> <key>: the direct audio service name or -, the call nonce, and the caller's ephemeral X25519 public key in base64
6Call accept<nonce> <key>: the nonce and the callee's ephemeral X25519 public key in base64
7Call declineThe nonce
8Call endThe nonce
9Lab probeMeasurement traffic, shown nowhere

Call signalling bodies are UTF-8 text separated by single spaces. A nonce is eight characters. A direct audio service name has the form call- followed by eight characters.

Voice frame

The payload of a voice packet over the mesh, and the whole datagram over the direct UDP path.

OffsetSizeField
04Sequence number, u32
4nChaCha20-Poly1305 ciphertext of one Opus packet
4 + n16Authentication tag
ParameterValue
Key32 bytes, one per direction, from HKDF-SHA256 over the X25519 shared secret
HKDF saltairlink-call-v1
HKDF infoc2a for caller to answerer, a2c for answerer to caller
Nonce12 bytes: eight zero bytes, then the sequence number
Associated dataThe same 12 nonce bytes
AudioOpus, 24 kHz, mono, 480 samples (20 ms) per packet, 24 kbit/s

On the mesh a 20 ms frame is 41 bytes of packet header, 4 of sequence number, about 60 of Opus at 24 kbit/s and 16 of tag: 121 bytes, or 48.4 kbit/s at 50 frames a second.

Identity file

Stored sealed at rest (see Security and privacy). The serialised form:

OffsetSizeField
01Version, 1
132Ed25519 private key
3332X25519 private key
65Display name, UTF-8

Bluetooth identifiers

ItemUUID
ServiceA17E0001-5D3A-4B7C-9E2F-A11C0DE0A17E
Inbox characteristic, write and write without responseA17E0002-5D3A-4B7C-9E2F-A11C0DE0A17E
Outbox characteristic, notifyA17E0003-5D3A-4B7C-9E2F-A11C0DE0A17E

The advertisement carries only the service UUID. A sender writes to the peer's inbox without response when it reached the peer as a central, and notifies through its own outbox when the peer subscribed to it. Chunks are sized to fit both limits.

Bonjour service types

TypeTransportUse
_airlink._tcpTCP, peer-to-peer allowedThe mesh over Wi-Fi. The service name is a random identifier chosen per launch
_airlinkcall._udpUDP, peer-to-peer allowedThe direct audio path for one call
_airlink-lab._tcpTCPLab mode's measurement collector, only when lab mode is on