Repository navigation
Airship file transfer
Airship is the file transfer on svc20. The glasses offer files to the phone, and the phone can offer files to the glasses. This page covers the message types, the payloads, the checksum, the link levels, the accept and refuse rules, the sender side and the named transfers.
Airship works the same way in both directions. The side that has a file is the sender, and the other side is the receiver. The glasses send media and bug reports. The phone sends MobileConfig tables and language packs. The receiver sends only types 2 and 7, and a sender acts only on those two. Messages carry their type on the DataX channel, described in DataX-channels.
| Type | From | Payload | Meaning | Receiver's answer |
|---|---|---|---|---|
| 1 | Sender | FileOffer | A file is on offer | Type 2 to accept, or type 7 to refuse |
| 2 | Receiver | Cursor | I have offset bytes whose adler32 is adler. Send from there |
|
| 3 | Sender | none | Reset: discard what you have and start at zero | Clear the buffer and the checksum |
| 4 | Sender | raw bytes | File data, appended at the current offset | None |
| 5 | Sender | Cursor | Check: where the sender thinks the receiver is | Type 2 with the receiver's own offset and adler32 |
| 6 | Sender | Cursor | Complete: the size and adler32 of the whole file | Check both, then type 7 |
| 7 | Receiver | FileID | Drop this file | |
| 8 | Sender | FileID | Removed. The sender dropped the transfer | Discard the partial file. No reply |
| 10 | Sender | none | The send queue is empty | No reply |
| 11 | Sender | FileStop | Stopped, with a reason | Discard the partial file. No reply |
| 12 | Glasses | FileOffer | An offer from the glasses' multiscope receiver | Handle as type 1 |
The payloads are protobuf-lite messages with proto3 implicit presence. A field equal to zero is not written.
| Message | Field | Type | Meaning |
|---|---|---|---|
| FileOffer | 1 | bytes, 16 | File uuid. Equals the asset id in the capture list |
| FileOffer | 2 | int64 | Size in bytes |
| FileOffer | 3 | string | Namespace, for example namespace_media
|
| FileOffer | 4 | string | Base name, often media
|
| FileOffer | 5 to 7 | not named | Not set by any offer seen so far |
| Cursor | 1 | uint64 | Offset in bytes |
| Cursor | 2 | uint32 | adler32 of every byte before the offset |
| FileID | 1 | bytes, 16 | File uuid |
| FileStop | 1 | bytes, 16 | File uuid |
| FileStop | 2 | enum | Reason: 0 DROPPED, 1 PRIORITY, 2 UNKNOWN, 3 LINK |
Bytes of a FileOffer, with the uuid and the names shown as placeholders:
0a 10 <16 uuid bytes> field 1, the uuid
10 <size varint> field 2, the size
1a 0f namespace_media field 3, the namespace
22 05 media field 4, the base name
The accept for a fresh file is offset 0 and adler32 of nothing, so the Cursor is 10 01. The offset is absent because it is zero, and the adler32 is 1.
A reader stops at the first wire type it does not know.
Airship uses adler32 with seed 1. Java's Adler32 and zlib give the same values. The glasses send a Cursor on type 6 that holds the size and the adler32 of the whole file. The receiver compares both with its own running values. The file counts as verified only when both match.
On a check (type 5) the receiver answers with its own offset and adler32. If the glasses see a smaller offset than they sent, they rewind. The sender does the same, and recomputes the adler32 from the start of the file when it rewinds.
Each queued transfer has a link requirement. The send queue has one active link. A transfer goes out only when the active link is at least as good as its requirement. The levels are ordinal.
| Level | Value | Chunk | Window |
|---|---|---|---|
| ALWAYS | not known | 1024 bytes | 64 KiB |
| LOW | 1 (unconfirmed) | 1024 bytes | 64 KiB |
| MEDIUM | 3 (unconfirmed) | 1024 bytes | 64 KiB |
| HIGH | 7 | 4096 bytes | 1 MiB |
The glasses pick the chunk size with (link and 7) == 7. So any value whose low three bits are 7 is HIGH. The glasses read the link type passed to the send. The window is how far the glasses send past the last offset the phone acknowledged in a cursor. They stop once sent is at least acknowledged plus the window.
The svc18 request 0x86 accepts link types 1, 3 and 7, as a bit mask of 0x8A. Link 1 is the Bluetooth link already up. Link 7 is HIGH. Capture requests for full resolution use link 7, so Wi-Fi is the active link for those transfers, which is why 4096-byte chunks arrive over Wi-Fi. See Radios-and-links and Wi-Fi-Direct.
Unconfirmed: the values 1 and 3 for LOW and MEDIUM are inferred from the accepted mask and the order.
Rates:
| Link | Rate |
|---|---|
| Bluetooth Classic RFCOMM | 120 to 165 KB/s |
| Wi-Fi Direct | about 10 MB/s, 20 MB in about 2 s |
The sender side in this page uses 1024-byte chunks on any link. A 187 MB send over Wi-Fi Direct took about 160 seconds with that chunk size.
A phone can accept or refuse any offer. Either way, the exchange ends with a drop on type 7.
Accept:
- The glasses send type 1. A channel holds one file at a time. A new offer on the channel drops any earlier partial file on it.
- The phone replies with type 2, offset 0 and the adler32 of nothing.
- The glasses send type 4 chunks. The phone appends each one at the current offset.
- The glasses send type 6. The phone compares the size and the adler32 with its own values, and replies with type 7.
Refuse:
- The glasses send type 1 for a namespace the phone declines.
- The phone replies with type 7 at once, with the uuid.
The rule is that every accept or refuse ends with a drop on type 7. A refusal is the drop itself. An accepted file ends with the drop that follows the complete. An offer that never gets its drop stalls the glasses' send queue, and every file queued behind it stops too.
| Namespace | Direction | Contents | Note |
|---|---|---|---|
namespace_media |
Glasses to Phone | Photos, videos, live clips, sensor logs and previews, each under its asset id | Capture files. See Capture-list |
bugreport |
Glasses to Phone | Bug report zip, after a request on svc7006 | A phone can accept it |
parfait |
Glasses to Phone |
metadata.json beside the photos |
A phone can refuse it |
telemetry |
Glasses to Phone | Device logs, hundreds of KB, continuously | A phone can refuse it |
mobileconfig |
Phone to Glasses | MobileConfig tables, zipped | Sent over the current link. See MobileConfig |
language_resource |
Phone to Glasses | A language pack zip, named like it_language_resource.zip
|
Sent over Wi-Fi Direct. See Assistant-voice-and-languages |
The phone side of the protocol sends a file as follows.
- Open a DataX channel on svc20.
- Send type 1 with a uuid. Wait for the answer.
- A type 7 answer means refused. A type 2 answer gives the offset to start from. The phone clamps the offset to the file size and recomputes the adler32 up to that offset.
- Send type 4 chunks. Chunks of 1024 bytes work on every link.
- Send type 5 checks as the transfer goes, with the offset and the adler32. Read any answer that has arrived. A type 7 at this point ends the send. A type 2 with a smaller offset means rewind to that offset.
- Send type 6 with the size and the adler32. Wait for the answer. A type 7 means success. A type 2 means the glasses want a part again, so the phone starts again from that offset.
The sequence below is one file offered by the glasses and accepted by the phone. The chunks are 1024 bytes on Bluetooth and 4096 bytes on Wi-Fi at link 7.
sequenceDiagram
participant Phone
participant Glasses
Glasses->>Phone: type 1 offer uuid size namespace base name
Phone->>Glasses: type 2 cursor offset 0 adler 1
loop each chunk
Glasses->>Phone: type 4 data
end
Glasses->>Phone: type 5 check, repeated
Phone->>Glasses: type 2 cursor our offset and adler
Glasses->>Phone: type 6 complete size and adler32
Phone->>Glasses: type 7 drop uuid
The diagram shows the common path for one file. A check can come several times in one file, and each one gets the phone's current position. A refused offer ends after the type 7 reply to the type 1.
The phone keeps one state per channel. A channel holds one file at a time, so a new offer on the same channel drops any earlier partial file.
stateDiagram-v2
[*] --> Idle
Idle --> Receiving: offer accepted, reply type 2
Idle --> Idle: offer refused, reply type 7
Receiving --> Receiving: type 4 data appends
Receiving --> Receiving: type 3 reset to zero
Receiving --> Receiving: type 5 check, reply type 2
Receiving --> Idle: type 6 complete, reply type 7
Receiving --> Idle: type 8 removed or type 11 stopped, discard
Receiving --> Idle: link lost, abandon all files
The diagram shows the phone as receiver. The state after a complete is Idle even when the checksum does not match. The phone still answers with a drop in that case.
- A refusal needs a drop on type 7. An unanswered offer stalls the queue behind it.
- The uuid is the asset id of a capture file. An offer whose uuid is not in the capture list still needs a reply and a drop.
- Only the opening frame of a message carries the service id and type. A chunk larger than one AirShield frame spans frames, and its continuations report 0. See Transport-and-framing.
- A 4096-byte chunk is 4104 bytes with its header and tag, so it does not fit one 4096-byte AirShield frame. A receiver that rejects the overrun loses the rest of the stream.
Reference code: Airship.kt
- The values of LOW and MEDIUM are not confirmed. The names are known, but no source gives the numbers.
- FileStop reason 2 (UNKNOWN) comes with the Bluetooth stall. Its cause is not known. Battery throttling is a candidate.
- Whether the glasses send type 12 in practice is not known. A phone can accept it.
- Whether a checksum mismatch on a type 6 happens in practice is not known.
- Whether the glasses slow their sends when checks arrive late is not known.
- Whether the glasses take 4096-byte chunks from the phone on a HIGH link is not known.
The glasses
The link
Identity
Media
Settings
Assistant