12.3. Формат пакета¶
Каждый байт, пересекающий линию между камерой и хостом, является частью пакета. Пакет начинается с 10-байтового заголовка, продолжается полезной нагрузкой переменной длины и заканчивается 4-байтовым завершающим CRC. Никаких других байтов на линии не появляется – как только хост увидел 2-байтовое синхрослово, следующие байты являются заголовком в этой точной последовательности.
12.3.1. Заголовок¶
Десять байт, упакованных без выравнивания. Каждое поле:
sync– 16-битное слово0xD5AAв порядке little-endian. Байт 0 на линии –0xAA, байт 1 –0xD5. Получатель, сканирующий байты, может найти начало пакета, выполняя поиск парыAA D5; всё, что перед ней, трактуется как мусор. Выбор значения не случаен:0xAAи0xD5редко встречаются в печатном тексте, и эта пара вряд ли возникнет случайно в середине полезной нагрузки.seq– один байт. Счётчик, увеличивающийся на единицу для каждого пакета, отправленного в заданном направлении. Получатель проверяет, что порядковый номер следующего пакета является ожидаемым; если нет, слой надёжности запрашивает повторную передачу.chan– один байт. ID канала, которому принадлежит этот пакет. Используемы каналы 0..31; встроенные каналыstdin,stdout,streamи (опционально)profileзанимают фиксированные ID, которые резервирует камера.flags– один байт. Битовое поле, сообщающее получателю, как интерпретировать пакет:бит 0
ACK– этот пакет является подтверждением предыдущего.бит 1
NAK– этот пакет отклоняет предыдущий.бит 2
RTX– этот пакет является повторной передачей.бит 3
ACK_REQ– отправитель хочет, чтобы этот пакет был подтверждён.бит 4
FRAGMENT– за этим следуют новые фрагменты более крупного сообщения.бит 5
EVENT– этот пакет несёт событие канала, а не данные.биты 6 и 7 зарезервированы.
opcode– один байт. Код команды или ответа. Библиотека протокола резервирует диапазоны кодов операций по назначению:0x00..0x0F– команды протокола (SYNC, GET_CAPS, SET_CAPS, STATS, VERSION).0x10..0x1F– системные команды (RESET, BOOT, INFO, EVENT, MEMORY).0x20..0x2F– команды каналов (LIST, POLL, LOCK, UNLOCK, SHAPE, SIZE, READ, WRITE, IOCTL, EVENT).
len– два байта, little-endian. Число байтов полезной нагрузки, следующих за заголовком. Нулевая длина допустима – многие подтверждения и небольшие команды не несут полезной нагрузки.crc– два байта. CRC-16 по предыдущим восьми байтам заголовка. Получатель, получивший заголовок с неверным CRC, отбрасывает весь пакет, даже не взглянув на полезную нагрузку.
12.3.2. Полезная нагрузка¶
Ноль или более байтов, рассматриваемых слоем формирования кадров как непрозрачные. То, что находится в полезной нагрузке, зависит от кода операции: для ответа CHANNEL_READ это фактические данные канала; для ответа GET_CAPS это небольшая фиксированная структура; для записи в канал это то, что отправил хост.
Максимальный размер полезной нагрузки зависит от размера буфера протокола камеры (см. таблицу по платам в protocol.init()). Сообщения длиннее предела разбиваются на фрагменты с установленным флагом FRAGMENT на всех, кроме последнего.
12.3.3. Завершающий CRC¶
Четыре байта, CRC-32 по полезной нагрузке. Улавливает повреждения, которые не способен увидеть CRC заголовка, особенно на длинных полезных нагрузках, где однобитовая ошибка в середине кадра иначе проскочила бы незамеченной.
Разделение проверки целостности между двумя CRC сделано намеренно. CRC заголовка защищает сами поля формирования кадра – особенно длину полезной нагрузки. Без отдельного CRC заголовка единичный перевёрнутый бит в байте длины заставил бы получателя прочитать неверное число байтов полезной нагрузки и полностью рассинхронизироваться с потоком байтов; при наличии CRC повреждённый заголовок отвергается сразу, и получатель заново сканирует следующее синхрослово. CRC полезной нагрузки затем защищает тело сообщения как отдельную задачу, так что перевёрнутый бит в данных сообщается как повреждённая полезная нагрузка, а не принимается ошибочно за ошибку формирования кадра.
Формат достаточно мал, чтобы пройти по нему байт за байтом, и тот факт, что каждый пакет имеет одну и ту же раскладку – синхрослово, затем заголовок, затем полезная нагрузка, затем CRC – означает, что написанный вручную парсер умещается в один экран кода. Вот почему крошечная реализация хоста на C, Python или Rust – это проект на выходные; библиотека протокола – это поддерживаемая версия на Python на каждой из сторон.