Binary / wire format¶
Модель записывается без имён полей, type tags, выравнивания и padding. Получателю заранее нужны класс модели и его настройки.
Заголовок и payload¶
По умолчанию используются Constructor(2), Flags(8), PayloadLength(4):
constructor (2 bytes) | flags (8 bytes) | body length (4 bytes) | fields
Header занимает 14 байт, даже без необязательных полей. PayloadLength хранит
число байтов всех полей после полного header. Header целиком исключён
из этой длины. __header__ позволяет изменить порядок, представление или
убрать элементы, включая весь header — Header и framing.
__constructor__ по умолчанию равен 1, __byte_order__ —
ByteOrder.BIG. Constructor сверяется с выбранным классом; библиотека
не назначает уникальные идентификаторы и не выбирает модель по этому числу.
Точный пример¶
from bytespec import ProtoModel, field
from bytespec.types import UInt8
class Packet(ProtoModel):
__constructor__ = 0x1234
number: UInt8
note: str | None = field(flag=0, prefix_length=1)
encoded = Packet(number=7, note="A").encode()
print(encoded.hex(" "))
12 34 00 00 00 00 00 00 00 01 00 00 00 03 07 01 41
12 34 constructor = 0x1234
00 00 00 00 00 00 00 01 flags: bit 0 set
00 00 00 03 payload length = 3
07 number = 7
01 41 note: byte length = 1, UTF-8 "A"
С note=None бит был бы сброшен, байты 01 41 отсутствовали бы,
а длина payload равнялась бы 1. Порядок полей определяется индексами,
номер flag его не меняет. Ненулевые defaults кодируются как обычные значения;
отдельных маркеров default в формате нет.
Порядок байтов¶
ByteOrder.BIG — big-endian, ByteOrder.LITTLE — little-endian.
Настройка распространяется на фиксированные целые и float, числовые части
заголовка и фиксированные length prefixes. Native alignment не используется.
from bytespec import ByteOrder
from bytespec.types import UInt16
class Point(ProtoModel):
__byte_order__ = ByteOrder.LITTLE
x: UInt16
print(Point(x=0x1234).encode()[-2:].hex(" "))
34 12
Для varint порядок байтов фиксирован алгоритмом и не зависит от модели.
Содержимое bytes и UUID.bytes не меняется; текст определяется
encoding. Вложенная модель всегда использует собственную настройку
__byte_order__.
Для UTF-16 порядок байтов текста задаётся самой кодировкой: выбирайте
utf-16-le или utf-16-be явно, если протокол требует конкретный вариант.
Значения внутри payload¶
Фиксированные целые занимают 1, 2, 4 или 8 байт. Знаковые числа используют
дополнительный код. Float записывается как IEEE 754 binary32/binary64.
Bool — один байт 00 или 01.
str содержит префикс длины закодированного текста, затем текст.
bytes содержит длину и исходные байты. FixedBytesCodec(N) и UUID
префикса не имеют. Datetime использует представление строки ISO, строковый
enum — представление своего .value. IntEnum использует VarInt
через EnumCodec; обычный int тоже использует VarInt.
date и time не имеют автоматического codec.
Поддерживаемые размеры префиксов и их пределы приведены в Строки, длины и настройки полей. Размер поля в байтах равен размеру префикса плюс его значению.
VarUInt и VarInt¶
VarUInt записывает группы по 7 бит, начиная с младшей. Старший бит байта
означает продолжение. Ноль записывается как 00, 127 — 7f,
128 — 80 01, 300 — ac 02. Допускаются только канонические записи
диапазона 0 .. 2**64 - 1: не более 10 байт, в десятом байте максимум
один значащий бит и нет продолжения. Лишняя нулевая старшая группа запрещена.
VarInt сначала применяет ZigZag: n >= 0 превращается в 2*n,
отрицательное n — в 2*abs(n)-1. Затем используется VarUInt.
Таким образом, 0 → 00, -1 → 01, 1 → 02, -2 → 03.
Поддерживается диапазон -2**63 .. 2**63 - 1.
Списки и вложенные сообщения¶
list[T]:
+------------------------+----------+----------+-----+
| byte length of items | item 0 | item 1 | ... |
+------------------------+----------+----------+-----+
model field / model list item (default header):
+-------+----------------+-------------+
| flags | payload length | own fields |
+-------+----------------+-------------+
Список не хранит число элементов. Decoder выделяет буфер указанной длины
и читает значения, пока не исчерпает его. Строки и bytes внутри списка
сохраняют свои префиксы. Модель сохраняет собственный header, пропуская
Constructor, и использует свой порядок байтов. Оставшийся header входит
в общую длину списка. При __header__ = () модель состоит только из полей;
границы определяются их codecs. Пример точных байтов — Как записывается header вложенной модели.
Границы и неизвестные данные¶
При чтении decoder обрабатывает только объявленные элементы header.
Если есть Constructor, он проверяется (при обычном отдельном чтении).
Если есть PayloadLength, проверяется наличие объявленного body.
В этом случае поля получают буфер, обрезанный по концу payload:
встроенный codec не может дочитать поле из следующего сообщения.
Вложенные модели ограничены payload родителя, элементы списка — его
отдельным буфером. Повреждённые значения отклоняются согласно Обработка ошибок.
Есть важное различие между двумя видами дополнительных байтов:
Внутри объявленного payload: после всех известных полей остаток пропускается. Неизвестные биты flags тоже игнорируются. Пропущенные байты и биты не сохраняются в экземпляре и исчезают после повторного
encode().После объявленного payload:
decode()выдаётDecodeError.decode_from()возвращает конец текущего сообщения, оставляя следующие байты вызывающему коду.
Без PayloadLength конец сообщения совпадает с концом известных полей:
неизвестный хвост не пропускается. decode_from() не может отделить
доступные байты следующего сообщения от текущего body, поэтому внешние
границы нужно ограничивать срезом.
Это не произвольная совместимость разных схем. Добавление данных в середину payload сдвигает последующие известные поля: у них нет индивидуальных тегов. Отсутствующее новое обязательное поле не восстанавливается через default. Меняя схему, проверяйте чтение в обе стороны.
Проверка границ не заменяет лимиты ресурсов: в текущем API нет
max_payload_size, max_string_size, max_list_elements или
max_depth. При работе с внешними сообщениями размер входа и нужные
приложению ограничения задаются вне библиотеки. В формате нет checksum;
изменение байтов, которое остаётся корректным значением, само по себе не
обнаруживается.
Точные сигнатуры и настройки классов собраны в API Reference.