‹ All AirLink docs

AirLink

How it works

The two transports, flooding with a hop limit, retries and receipts, identity, and how a call is set up and carried.

AirLink runs two transports at the same time and presents them to the mesh as one. A packet goes out over every link that exists, and a copy that arrives by the other road is discarded by its message identifier. Nothing above the transport layer knows which radio carried a packet.

Bluetooth Low Energy

Every device is a GATT peripheral and a central at the same time, so two AirLink devices find each other without deciding who goes first. Usually both paths come up, and whichever is ready first carries traffic.

  • Advertising. The advertisement carries only AirLink's 128-bit service UUID. iOS does not let an app put data in an advertisement, so every byte of payload travels over a connection.
  • Scanning. Always filtered by the service UUID, because a wildcard scan returns nothing once an iOS app is in the background.
  • Data. Peers write into an inbox characteristic and receive through notifications on an outbox characteristic.
  • Chunk size. Read fresh for every send: the smaller of the link's write limit and its notification limit, and never below 20 bytes. When a link reports nothing, 180 bytes.
  • Backpressure. A link with 64 chunks already waiting accepts no more, so a slow radio sheds whole packets instead of building up seconds of delay.
  • Reconnection. A dropped peripheral is reconnected by reissuing the connect, which iOS holds pending indefinitely. State restoration identifiers let iOS hand a relaunched app its existing links.

Wi-Fi

Each device publishes a Bonjour service of type _airlink._tcp and browses for the same type. The connection parameters include peer-to-peer, so two Apple devices can form a direct Wi-Fi link with no router, and devices on the same network find each other through it as well.

  • Who dials. Each device names its service with a random identifier. When two devices see each other, only the one with the lower identifier dials, so exactly one connection forms with no negotiation.
  • Keepalive. TCP keepalive after 5 seconds idle, probing every 5 seconds, giving up after 2 missed probes. A peer that walked away is noticed in seconds and the connection is redialled when it returns.
  • Dial timeout. A connection that is not ready within 10 seconds is cancelled.
  • Framing. None needed. Packets are self-framing, so the TCP stream is fed straight to the same reassembly as Bluetooth.

Reassembly

Each link has its own stream chunker. It buffers bytes, finds the two-byte packet magic, reads the header and waits for the declared payload length. If a chunk is lost mid-packet, the chunker scans forward to the next magic and loses only that packet. Anything that grows past 64 KiB without forming a packet is discarded as garbage.

Routing by flooding

There is no route discovery. A phone in a pocket has no stable links worth remembering, so AirLink floods with a hop limit and a duplicate table.

For every packet that arrives, in this order:

  1. Duplicate check. If the 16-byte message identifier has been seen, drop it. Nothing may come before this check, because it is what keeps a flood from becoming a storm.
  2. Age check. Drop a packet more than six hours old, or more than fifteen minutes in the future.
  3. Voice. Deliver it if it is for this device. Never relay it.
  4. For this device. Deliver a direct message addressed here and do not relay it further.
  5. Otherwise. Deliver broadcasts. If the hop limit is above one, decrement it and relay the packet on every link.

Relaying happens only after the packet has passed decoding and these checks.

Adaptive hop limit

A message's hop limit is set at its origin from the number of devices in direct range:

Devices in rangeHop limit
Fewer than 38
3 to 56
6 or more5

In a thin chain, depth is what reaches the recipient. In a crowd, extra hops only multiply redundant copies.

Duplicate table

The table holds up to 8,192 identifiers for up to 30 minutes, evicting the oldest first.

Retries and receipts

A message this device sends is kept in an outbox and offered to every peer that connects, oldest first and once per peer. That peer may be the recipient, or a device connected to the recipient that will relay it.

  • Delivery is confirmed only when the recipient's sealed receipt, carrying the original message identifier, arrives back. The message then leaves the outbox.
  • Patience. The outbox gives up on a message after 24 hours. Because every receiver drops packets older than six hours, a message can in practice be delivered only within six hours of being sent.
  • Lifetime. The outbox lives in memory. It lasts as long as AirLink keeps running.
  • Relays do not store. A device relaying someone else's packet forwards it to its current links at once and keeps no copy.

Call signals and receipts are never queued. A call signal that cannot be delivered now is meaningless later, and would act on whatever call is live when it finally arrived.

Identity and numbers

On first use a device generates two long-term keys: Ed25519 for signing and X25519 for key agreement. They are written once, sealed, with owner-only file permissions. If the file exists but cannot be read, for example because the Keychain is locked, AirLink runs that session on a temporary identity and leaves the file untouched rather than overwrite the only copy of the keys.

  • Fingerprint. The first 8 bytes of SHA-256 over the signing key followed by the agreement key. It addresses packets.
  • Number. The first 40 bits of the fingerprint in Crockford base 32, shown as two groups of four characters. The alphabet leaves out I, L, O and U. Three example numbers are reserved and never issued.

Because the number is a function of the keys, and every message is signed by those keys, a device cannot claim someone else's number and then speak as them.

Presence

A device announces itself with a hello: its contact card, which holds its name and its two public keys, sent to everyone with a hop limit of two. It announces when a link comes up and again 1.5 seconds later, because the first hello can race the other side's setup, and again whenever the app comes to the front. Hellos are how two people who have never met appear in each other's lists.

Sealing a message

Every message is sealed to the recipient with HPKE (RFC 9180) using X25519, HKDF-SHA256 and ChaCha20-Poly1305. Inside the ciphertext travel the sender's two public keys and an Ed25519 signature over the message identifier and the text. The recipient opens the envelope and then verifies the signature against the key the sender claims. The message identifier is also bound as associated data, so a relay cannot re-label a genuine ciphertext.

Before sealing, the plaintext is padded to the next of 256, 512, 1,024, 2,048, 4,096 or 8,192 bytes, so the size on the air reveals only a bucket.

Calls

Signalling

A call's signals travel as ordinary sealed messages, never queued:

  1. Invite. The caller creates an ephemeral X25519 key and an eight-character call nonce, publishes a direct audio service on the local network if it can, and sends the invite. The invite repeats every two seconds while ringing, so a device that joins the mesh mid-ring still rings.
  2. Ring. The callee derives the call keys from the invite and rings. A ring that hears no invite for 12 seconds ends as missed.
  3. Answer. The callee sends its own ephemeral key back with the nonce, starts audio over the mesh and joins the caller's direct audio service.
  4. Connected. The caller derives the same keys and starts audio.
  5. End or decline. Either side sends the nonce with an end or decline signal. After 60 seconds of ringing with no answer, the caller ends the call.

A device already in a call declines a new invite. The last eight ended nonces are remembered, and a late invite for one of them is declined rather than ringing again.

Keys

Both ephemeral keys produce one shared secret. HKDF-SHA256 with the salt airlink-call-v1 derives two 32-byte keys, one per direction, so the two counters can never reuse a nonce under the same key. Each 20 ms frame is sealed with ChaCha20-Poly1305, with a 4-byte sequence number as the nonce and as associated data. Every frame costs 20 bytes of overhead.

Audio

Speech is encoded with Opus at 24 kHz mono in 20 ms frames at 24 kbit/s, using Apple's own encoder. Opus is what fits a call inside a Bluetooth link: raw speech at this rate is about 48 kB a second, Opus about 3 kB.

Two roads

Every call starts on the mesh, the road the invite has just proved works. Voice packets on the mesh have a hop limit of one, are never relayed and never queued, and go to the link the other person was last seen on, or to every link when that is not yet known.

At the same time, each side tries a direct UDP path found through Bonjour, type _airlinkcall._udp. Frames go down both roads until one arrives over UDP. From then on, while a direct frame has arrived in the last 2.5 seconds, only the direct road is used. If it goes quiet, the mesh copy resumes. A 64-frame sliding replay window makes sure a frame that arrives both ways plays once.

Echo

Both apps first ask the system for its voice processing, which includes echo cancellation. Where a Mac cannot run its microphone and speakers together that way, AirLink uses separate capture and playback engines and its own echo canceller: a 4,096-tap normalised LMS filter, about 170 ms at 24 kHz, with step size 0.5, running in the codec's own format. It learns only while the far end is audible, and once it has a model, only from blocks that model already explains, so it does not learn to cancel the person speaking. If it makes the signal louder for about a second it resets, and after three resets it switches itself off for the rest of the call.

On iPhone and iPad

Calls are reported to CallKit, so they use the system's incoming call screen and controls. They are not added to the phone's recent calls. If the system refuses the integration, calls continue inside the app, as they do on the Mac.