DataX channels
DataX is the message layer inside an AirShield frame. It carries service traffic over numbered channels, and one frame can hold several messages back to back. This page covers the header, tags, opening frames and continuations, fragments, channel ids, the channel registry rules, status frames and the error codes on a channel.
Where DataX sits
Each AirShield frame carries plaintext that DataX splits into messages. Every message has a 4-byte fixed header, optional extension tags and a payload. The AirShield layer is described in AirShield frames and ciphers.
Fixed header and extension tags
The fixed header is 4 bytes. The extension tags follow it, and each tag is 4 bytes.
| Byte | Bits | Meaning |
|---|---|---|
| 0 | 7 | hasExtension. Set when at least one tag follows. |
| 0 | 5 to 0 | payload_len, high 6 bits |
| 1 | 7 to 0 | payload_len, low 8 bits |
| 2 to 3 | 15 to 0 | channelId, big-endian. Bit 15 is the originator bit. |
payload_len is 14 bits, so the largest message is 16383 bytes. It counts the extension tags as well as the payload. The bytes on the wire for one message are 4 + payload_len. The application payload is payload_len - (header_len - 4), where header_len is 4 plus 4 for each tag.
| Byte | Bits | Meaning |
|---|---|---|
| 0 | 7 | continuation. Set on every tag except the last. |
| 0 | 6 to 0 | Tag id |
| 1 | 7 to 0 | d1. Used only by tag 5 (priority). |
| 2 to 3 | 15 to 0 | Value, big-endian |
| Tag id | Name | Value | Effect on parse |
|---|---|---|---|
| 1 | service id | service number | Sets the service. A value of 0 closes the channel. |
| 2 | message type | type number | Sets the message type. |
| 3 | error id | status code | Marks a status frame. The payload is empty. |
| 4 | message type, closed form | type number | Sets the message type and closes the channel. |
| 5 | QoS | d1 priority, value experience tag |
Priority in d1. The meaning of the value is unconfirmed. |
The phone writes tags in the order service, type, error, QoS. A message above 16383 bytes does not fit in one header.
Opening frame, continuations and the type cache
A channel's first message carries the service tag (tag 1) and the message type tag (tag 2). Later messages on the same channel may omit both. The glasses omit the type tag when the type has not changed since the last message on that channel.
A receiver reads a message with no tags as service 0 and type 0. It then takes the type from the last type seen on that channel. A status frame keeps its own type, which is absent.
| Frame | Tags present | Parsed as |
|---|---|---|
| Opening frame of a service channel | service, type | Service and type set. The registry binds the channel. |
| Typed message on a known channel | type | Service 0. Registry lookup gives the service. |
| Continuation message | none | Type taken from the channel cache. |
| Status frame | error | Error id set. Payload empty. |
Example. A status frame that confirms a message with OK on channel 0x0002:
80 04 00 02 03 00 00 00
Byte 0 0x80 sets hasExtension. Bytes 1 and 2 to 3 give payload_len 4 and channel 0x0002. The tag 03 00 00 00 is error id 0, which is OK.
A message with no tags, 17 bytes of payload, on channel 0x8002:
00 11 80 02
A sender should send a bare continuation when the type is unchanged. Tagging every chunk may starve the peer's decoder during an audio burst. Unconfirmed: the size of that effect. Airship chunks carry the type tag on every message, and the glasses accept them.
Fragments and reassembly across frames
A message can be longer than the rest of the AirShield frame that carries its header. The glasses do this all the time: airship transfers use 1024-byte messages, and live video chunks fill frames to the 4095-byte limit. The rest of the message comes at the start of the next frame as raw bytes with no header.
The frames on one link are one byte stream once their pads are off. A receiver strips each frame's pad, appends what is left to the bytes still owed, and cuts messages by 4 + payload_len. A message can span more than two frames.
The example below is a frame of 4096 bytes of plaintext. The message starts at offset 3084 with a 4-byte header that declares a 1024-byte payload.
| Part of frame 1 | Bytes | Notes |
|---|---|---|
| Earlier messages | 3084 | Whole messages |
| Header | 4 | payload_len 1024, no tags |
| Head of the payload | 1007 | Data |
| Pad | 1 | 0xC1 |
Frame 1 is 4096 bytes. The message owes 17 more payload bytes. Those 17 bytes start frame 2.
flowchart LR
subgraph F1["Frame 1, 4096 bytes"]
A["Earlier messages<br/>3084 bytes"] --> B["Header<br/>payload_len 1024"]
B --> C["Head of payload<br/>1007 bytes"]
C --> D["Pad<br/>1 byte 0xC1"]
end
subgraph F2["Frame 2"]
E["Rest of payload<br/>17 bytes"] --> G["Next message"]
end
C -. "held in the fragment slot" .-> E
The diagram shows one 1024-byte message split across two AirShield frames. The tail of frame 1 is padding. Frame 2 starts with the 17 bytes that finish the message.
The walker works in this order.
- Strip the pad from the end of the frame. The pad is
Ncopies of the byteN + 0xC0, withNfrom 1 to 16. AllNbytes must match. A block-aligned frame has no pad. - Put the bytes still owed from the last frame in front of what is left.
- Read the header at the current offset. Compute
wire_total = 4 + payload_len. - If the bytes left hold
wire_totalbytes, take the message and move on. - If not, keep the bytes left for the next frame.
The phone sends long messages the same way: a message longer than one frame runs on into the next frames. A message above 16383 bytes does not fit in one header. Services that need more cut their messages themselves, such as svc15. See Assistant protocol.
Channel ids and the originator bit
A channel id is 16 bits. Bit 15 (0x8000) is the originator bit. The side that opens a channel sets it on every frame it sends on that channel. The other side answers on the same channel with the bit clear. The answer uses the mirror id, which is the same id with bit 15 cleared.
| Channel id | Meaning |
|---|---|
0x8001 |
Link setup channel. RequestEncryption and EndLinkSetup use it. |
0x0001 |
Mirror of 0x8001. The glasses' EnableEncryption and the EndLinkSetup acknowledgement use it. |
0x8002 and up |
Service channels opened by the phone |
0x0002 and up |
Replies on the phone's service channels |
The phone allocates its service channels from a counter that starts at 2. Channel 0x8001 is taken by link setup. A counter that started at 1 would hand that id to the first service channel and break the link.
Each side allocates ids for its own channels. The glasses allocate theirs in the same range. The Neural Band numbers its channels from 0x1001. Both sides can therefore use the same low number. The originator bit tells the two apart. A frame with bit 15 set is on a channel the peer opened. A frame with bit 15 clear is a reply on a channel this side opened.
The glasses can open a channel with the same low number as a phone channel, and both channels carry traffic. See the sequence below.
Channel registry rules
The registry answers one question: which service is this channel talking to, right now. A phone follows these rules.
- Keep two maps, both keyed on the low 15 bits of the channel id.
localholds channels this side opened.peerholds channels the glasses opened. - An inbound frame with bit 15 set goes to
peer. An inbound frame with bit 15 clear goes tolocal. - Allocate each new local id with bit 15 set, and bind it to its service at once.
- When a frame from the glasses names a service, record that service for the channel in
peer. The glasses name the service on the first message of a channel only. Later frames read service 0 and need this lookup. - For an inbound channel, return the bound service, or 0 when nothing has named one.
- A frame with
isClosedremoves the peer binding for its channel.
A plain counter works. It does not need to move above the channels the glasses use, because the originator bit keeps the two sides apart. Each connection starts again with empty maps and a counter at 2. Ids are not reused within one connection. Unconfirmed: whether the glasses expect a close frame for a channel the phone opened.
The phone keeps one channel per service for each connection. The glasses answer on every open channel of a service. One channel per request would multiply every reply.
The sequence below shows two channels with the same low number, one from each side.
sequenceDiagram
participant Phone
participant Glasses
Phone->>Glasses: 0x8002 opens svc28 with type tag
Glasses->>Phone: 0x0002 reply on the phone channel
Glasses->>Phone: 0x8002 opens svc20 with type tag
Phone->>Glasses: 0x0002 status OK on the glasses channel
Phone->>Glasses: 0x8002 message with no tags stays on svc28
Glasses->>Phone: 0x0002 reply to the no-tag message
The sequence shows two channels that both use low number 2. The phone opened 0x8002 for svc28. The glasses opened their own 0x8002 for svc20. Each side keeps the two in separate maps, so the frames do not mix. The phone answers the glasses' channel on its mirror 0x0002.
Status frames
A status frame is a bare header with an error tag and no payload. It goes on the channel it answers, under the rule in Channel ids and the originator bit. OK on the wire is 0x0000. The example in the earlier section is the OK frame on channel 0x0002.
Error codes on a channel
These codes arrive as the error tag of a status frame. The names are as used in Meta's DataX library.
| Code | Name | Meaning |
|---|---|---|
0x0000 |
OK | The peer accepted the message. Also the end of a reply sent as several messages, such as a svc9000 fetch. |
0x1000 |
SUCCESS | The svc36 trust result, and the answer to the glasses' EndLinkSetup. Not the generic OK. |
0xC001 |
service_not_found | The service is not registered on this stream. On a link without a scope, every service except 30, 36 and 79 answers this. |
0xC002 |
The channel is gone: the peer no longer has it, for example after its service restarted. | |
0xC004 |
Bad or oversized data. | |
0xD001 |
unknown_type | The service exists, but it does not handle that message type. |
0xD004 |
The message type is wrong for a request, for example a response type. | |
0xD010 |
Bad request. The protobuf body failed to parse. | |
0xE001 |
write_would_block | The peer could not take the write. |
Where these codes appear on the wire:
- The phone answers pushes from the glasses on services 30 and 79 with
0xC001. - A channel answered
0xC001or0xC002stays dead, also after its service comes up. The phone opens a new channel for the service. The status comes on the mirror of the phone's channel, without bit 15. 0xD001does not mean one thing everywhere. On service 31, the settings service, the glasses return it for two cases. One is an unknown message type. The other is a type-2 body that fails to parse.0xD010shows up on a Wi-Fi join. The glasses reject aJoinWifiDirectGroupmessage with0xD010when field 4 is missing, instead of answering with a status. See Wi-Fi Direct.
Pushes from the glasses after link setup
After link setup, the glasses open channels of their own, for example svc20. Some pushes need a reply from the phone.
| Push from the glasses | Phone reply |
|---|---|
| svc36 type 4096 or 4097 on their own channel | Success status, as trust. See Identity and trust. |
| svc30 (device-peer trust) | 0xC001 |
| svc79 (constellation auth) | 0xC001. The phone sends no svc79 traffic of its own. |
| svc4 (connection monitor), type 0 with a 4-byte counter | OK status. Unanswered, the glasses send one per connection. Answered, they count up every 1 to 4 s on one channel. |
| Any other service | 0xC001 unless the phone handles the service. Unconfirmed: what the glasses do with an unanswered push. |
The glasses also push a type 4096 message of about 551 bytes on their own channel after a change of owner. Its size suggests a certificate. Unconfirmed: its content. See Identity and trust.
After the trust exchange, the phone waits for the glasses' EndLinkSetup on channel 0x8001. It answers with success on 0x0001. The phone does not need a fixed settle delay after that. Unconfirmed: whether the glasses need one.
Reference code: Header.kt, DataXConnection.kt, ChannelRegistry.kt
Open questions
- What the glasses do with a push that gets no answer is not confirmed.
- Whether tagging the type on every message starves the glasses' decoder in an audio burst is not confirmed.
- Whether the glasses need the phone's channel ids to skip the ids they use is not confirmed.
0xD001has more than one meaning, and its meaning depends on the service. How to tell them apart on a given channel is not known.- The content of the 551-byte type 4096 push from the glasses is unknown.
- No full table of error codes per service is known.