12.3. Формат пакета

Каждый байт, пересекающий линию между камерой и хостом, является частью пакета. Пакет начинается с 10-байтового заголовка, продолжается полезной нагрузкой переменной длины и заканчивается 4-байтовым завершающим CRC. Никаких других байтов на линии не появляется – как только хост увидел 2-байтовое синхрослово, следующие байты являются заголовком в этой точной последовательности.

Горизонтальная схема пакета протокола, показывающая 10-байтовый заголовок (синхрослово, порядковый номер, ID канала, флаги, код операции, длина полезной нагрузки, CRC заголовка), за которым следует полезная нагрузка переменной длины и 4-байтовый CRC полезной нагрузки.

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 на каждой из сторон.