Protocol Packets¶
The Ambit protocol messages need to be transferred between application and watch. The transfer method of choice is that of USB-HID interrupt transfers. These interrupt transfers have one important requirement that needs to be satisfied: every single transfer must send a fixed amount of bytes. The value for that amount is available for each USB interrupt endpoint in its \(wMaxPacketSize\) field.
This means that messages can to be too short or too long for a single USB interrupt transfer. To address that, the Ambit protocol messages are “stuffed” into Ambit protocol packets. Each such protocol packet is exactly \(wMaxPacketSize\) bytes long. Protocol messages that are too short are padded, those that are too long are split up over multiple packets. Hence, messages always correspond to one or more packets. Conversely, each packet always contains (part of) a message.
Structure¶
For administrative purposes, each packet starts with a small header of its own followed by a cyclic redundancy check (CRC). Next comes a packet payload followed by a second CRC. The CRC values are included so that header and payload can be checked for errors. Left over space is padding and should be ignored. Each packet but the last holds as much data as will fit. Only the last packet for a message will have any padding.
Writing the packet structure down in Augmented Backus–Naur Form (ABNF) gives
packet ::= packet-headerCRCpacket-payloadCRCpadding packet-header ::=markeroffsettypesizeindexpacket-payload ::= *OCTET padding ::= *OCTET
Taking into account the sizes of the various components, the maximum payload size for a single packet works out to \(wMaxPacketSize - 10\) bytes.
Illustrating the layout for a 64 byte packet containing 42 bytes of a message payload gives the picture below. As far as the packets are concerned, only the red outlined parts are of interest.
Marker¶
marker ::= %x3f
The first byte of every Ambit protocol packet is always 0x3f. In
ASCII that translates to a question mark. Any payload from a USB-HID
transfer that starts with something else is most probably not an
Ambit protocol packet.
Offset¶
offset ::= %d8-%d254
The single byte integer in the second byte of the packet tells where one can find the second of the cyclic redundancy checks (CRC-2). As payload sizes vary, the check value is not located at a fixed position. This byte makes it easy to find. The offset is always equal to the sum of header and payload size, per packet layout.
From the packet layout it also follows that its value is always between \(8\), the size of a packet header and the first CRC, and \(wMaxPacketSize - 2\), both inclusive. Combined with the one byte nature for this offset, this means that \(256\) is the largest \(wMaxPacketSize\) value that can be accommodated.
Note
The \(wMaxPacketSize\) value has to be a power of 2 per USB specification.
Type¶
type ::= starter | trailer starter ::= %x5d trailer ::= %x5e
Packets come in two different types: starter and trailer packets. A
starter packet has a type byte equal to 0x5d, a trailer packet
uses 0x5e. The type byte is at an offset of 2.
When Ambit protocol messages are “stuffed” into packets, only the first part of the message travels in a starter packet. Any other packets that may be necessary to send the whole message travel in trailer packets.
Size¶
size ::= %d0-%d246
Although very easily computed from the check value offset, the payload size is also stored at offset 3 in the packet. It gives the number of bytes of the payload that is sandwiched between the two check values (CRC-1 and CRC-2).
Notwithstanding the ABNF, its value is always between \(0\) and \(wMaxPacketSize - 10\), both values inclusive. So for a watch with a \(wMaxPacketSize\) of \(64\), the ABNF would become
size ::= %d0-%d54
Index¶
index ::= %d1-%d65535
All packets include a little-endian, two byte integer index at offsets 4 and 5. The index of the starter packet counts the number of packets that make up a single message. Indices for the message’s remaining trailer packets increase from one to one less than the index value in the starter packet. So for an index value of \(n\) in the starter packet, the indices of trailer packets will run from \(1\) through \(n - 1\).
Combined with the payload size constraints, this means that the Ambit protocol message size has an upper bound of \(65535 (wMaxPacketSize - 10)\), where \(wMaxPacketSize\) is \(256\) at most, per offset constraints.
CRC¶
CRC ::= 2OCTET
They have been mentioned a few times above already, but Ambit protocol packets use a cyclic redundancy check (CRC) pair to guard against packet corruption going unnoticed. Many CRC algorithms exist but the algorithm used by the Ambits goes by the name of CRC-16/CCITT FALSE (or something similar).
-
CRC-1 The first CRC, CRC-1, covers the type, size and index, that is, the four bytes starting at offset 2.
-
CRC-2 The second CRC, CRC-2, covers the payload as well as everything covered by CRC-1. In practice, this means that one would normally compute CRC-1 first and use that as the seed when continuing the computation over the payload bytes to get CRC-2.
Note
Code for this widely used algorithm should be easy to find.
The Python crcmod module supports it as one of its predefined
CRC algorithms and can generate code for C/C++. The Wireshark
code includes a crc16_x25_ccitt or crc16_x25_ccitt_seed
function (depending on the version) that implements it as well.