CBOR
JSON понятен человеку, но микроконтроллеру дорог: разбор текста, экранирование, перевод чисел из десятичной записи. CBOR хранит ту же модель данных в двоичном виде, где целое число 10 занимает один байт, а декодер умещается в несколько сотен строк.
Обновлено Редакция GAW
CBOR (Concise Binary Object Representation) — двоичный формат сериализации данных, определённый в RFC 8949 (STD 94). Его модель данных — расширенная модель JSON: те же числа, строки, массивы и объекты-карты, к которым добавлены байтовые строки и теги (разд. 1). Главные цели стандарта (разд. 1.1) — кодер и декодер, умещающиеся в памяти самых ограниченных узлов, декодирование без схемы и лишь затем компактность данных, которая обязана быть не хуже JSON. Именно поэтому CBOR стал штатной полезной нагрузкой CoAP — у него есть идентификатор Content-Format 60 (разд. 9.4) и медиатип application/cbor (разд. 9.3).
Первый байт решает всё
Каждый элемент начинается с байта, где старшие 3 бита — основной тип, а младшие 5 бит — «дополнительная информация» (разд. 3). Значения 0–23 — это сам аргумент; 24, 25, 26, 27 говорят, что аргумент лежит в следующих 1, 2, 4 или 8 байтах в сетевом порядке байтов (старший первым, см. порядок байтов); 31 — неопределённая длина, завершаемая кодом «break».
| Тип | Что кодирует | Аргумент | Пример (прил. A) |
|---|---|---|---|
| 0 | беззнаковое целое | само значение | 10 → 0x0a; 1000 → 0x1903e8 |
| 1 | отрицательное целое | −1 − N | −1 → 0x20 |
| 2 | байтовая строка | длина в байтах | — |
| 3 | текст UTF-8 | длина в байтах | "a" → 0x6161 |
| 4 | массив | число элементов | [1, 2, 3] → 0x83010203 |
| 5 | карта «ключ — значение» | число пар | → 0xa0 |
| 6 | тег | номер тега | тег 1 — секунды с 1970 года |
| 7 | простые значения и float | 20 false, 21 true, 22 null, 23 undefined; 25, 26, 27 — float16/32/64 | 5.5 → 0xf94580 |
Текст в CBOR никогда не экранируется: перевод строки — это байт 0x0a, а не последовательность \n (разд. 3.1). Карта может иметь ключами не только строки, но и целые — этим пользуется SenML, заменяя "n", "v", "t" на 0, 2, 6.
Числа и время
Целое 10 занимает один байт, 500 — три (0x19 и два байта 0x01f4), −500 кодируется типом 1 с аргументом 499 (разд. 3.1). Для чисел с плавающей точкой есть три размера по IEEE 754, и «предпочтительная сериализация» (разд. 4.1) выбирает кратчайший, сохраняющий значение: 5.5 укладывается в половинную точность, три байта. Время передают тегами: тег 0 — строка даты по RFC 3339, тег 1 — секунды от 1970-01-01T00:00Z, целые или дробные (разд. 3.4.1, 3.4.2). Двоичные данные — показания, образ прошивки, подпись — идут байтовой строкой как есть, без Base64.
Сравнение с JSON
Объект {"a": 1, "b": [2, 3]} в CBOR занимает 9 байт: a2 61 61 01 61 62 82 02 03 (прил. A, табл. 6). Та же запись в JSON без пробелов — 17 байт (наш расчёт). Разница заметнее на числах и байтовых строках, а на длинных текстах почти исчезает: текст в обоих форматах хранится в UTF-8. Для микроконтроллера важнее другое: CBOR читается последовательно, длина каждого элемента известна из заголовка, и декодер не ищет кавычки и запятые. Потоковые данные можно писать массивом неопределённой длины и закрывать его байтом «break», когда измерения закончились (разд. 3.2). Другой вариант потока — CBOR Sequence (RFC 8742): элементы пишутся подряд без массива и разделителей, медиатип application/cbor-seq, Content-Format 63; декодер отдаёт элементы по мере поступления байтов и замечает обрезанный последний элемент, но не пропажу целых элементов в конце потока (RFC 8742, разд. 2).
Где встречается
Полезная нагрузка CoAP и LwM2M, формат SenML в варианте application/senml+cbor, подписи COSE, сообщения через MQTT, когда трафик дорог. Как встроить компактный декодер в прошивку и проверять его на эталонных байтах из приложения A RFC 8949 — в статье о реализации протоколов.
Частые вопросы
Насколько CBOR компактнее JSON?
Зависит от данных; RFC 8949 ставит компактность данных ниже компактности кода и лишь требует не быть больше JSON. Пример из прил. A: объект {"a": 1, "b": [2, 3]} кодируется в 9 байт — 0xa26161016162820203, тогда как та же запись в JSON без пробелов занимает 17 байт (наш расчёт). Целые до 23 занимают один байт, до 255 — два, до 65 535 — три; главный выигрыш — числа и двоичные данные, которые в JSON пришлось бы кодировать в Base64.
Как по первому байту понять тип значения CBOR?
Старшие 3 бита — основной тип: 0 беззнаковое целое, 1 отрицательное, 2 байтовая строка, 3 текст, 4 массив, 5 карта, 6 тег, 7 простое значение или число с плавающей точкой. Младшие 5 бит — аргумент: до 23 он лежит прямо здесь, 24, 25, 26, 27 означают, что аргумент занимает следующие 1, 2, 4 или 8 байт, 31 — неопределённая длина. Например, 0x83 — массив из трёх элементов, 0xa0 — пустая карта, 0xf5 — true.
Как CBOR передаёт время?
Двумя стандартными тегами (разд. 3.4.1 и 3.4.2 RFC 8949): тег 0 оборачивает текстовую дату в формате RFC 3339, тег 1 — число секунд от 1970-01-01T00:00Z UTC, целое или с плавающей точкой для долей секунды. Для дат после начала 2106 года тег 1 требует 64-битных целых; для микроконтроллера это значит, что декодер должен уметь принимать аргумент из 8 байт.
Источники
- C. Bormann, P. Hoffman. Concise Binary Object Representation (CBOR). RFC 8949, STD 94, December 2020 — разд. 1: модель данных — расширенная модель JSON, документ заменяет RFC 7049 без изменения формата; разд. 1.1: цели — компактный код кодера и декодера (узлы класса 1 по RFC 7228), декодирование без схемы, компактность данных вторична, JSON как верхняя граница размера, расширяемость; разд. 3: первый байт — 3 бита основного типа и 5 бит дополнительной информации, значения до 23 — сам аргумент, 24–27 — аргумент в следующих 1, 2, 4 или 8 байтах в сетевом порядке, 31 — неопределённая длина; разд. 3.1, табл. 1: типы 0 беззнаковое целое, 1 отрицательное (−1 − N), 2 байтовая строка, 3 текст UTF-8 без экранирования, 4 массив, 5 карта пар, 6 тег, 7 простые значения и float; примеры: 10 — один байт 0b000_01010, 500 — 0b000_11001 и 0x01f4, −500 — 0b001_11001 и 0x01f3; разд. 3.2: массивы, карты и строки неопределённой длины с завершающим «break»; разд. 3.3, табл. 3 и 4: доп. информация 25, 26, 27 — IEEE 754 half, single, double; простые значения 20 false, 21 true, 22 null, 23 undefined; разд. 3.4.1: тег 0 — строка даты RFC 3339; разд. 3.4.2: тег 1 — секунды от 1970-01-01T00:00Z, целое или float, после 2106 нужны 64-битные целые; разд. 4.1: предпочтительная сериализация — кратчайшая форма аргумента и кратчайший float, сохраняющий значение, 5.5 — 0xf94580; разд. 9.3: медиатип application/cbor, расширение .cbor; разд. 9.4: CoAP Content-Format ID 60; прил. A, табл. 6: 0 — 0x00, 24 — 0x1818, 1000 — 0x1903e8, −1 — 0x20, [1, 2, 3] — 0x83010203, {"a": 1, "b": [2, 3]} — 0xa26161016162820203, пустая карта — 0xa0
- C. Bormann. Concise Binary Object Representation (CBOR) Sequences. RFC 8742, February 2020 — разд. 2: CBOR Sequence — ноль или более закодированных элементов CBOR, записанных подряд без разделителей и без признака конца, поскольку каждый элемент самоограничен; потоковый декодер выдаёт элементы по мере поступления байтов; формат надёжно обнаруживает обрезку последнего элемента, но не пропажу целых элементов в конце; разд. 6.1: медиатип application/cbor-seq; разд. 6.2, табл. 1: CoAP Content-Format 63
Нашли ошибку? ·
Смотрите также
- CoAPПротокол запрос-ответ поверх UDP для ограниченных устройств: типы сообщений, методы, таймауты, порт 5683
- SenMLФормат измерений для HTTP и CoAP: массив записей с именем, единицей, значением и временем, в JSON, CBOR, XML
- Порядок байтовLittle-endian и big-endian в памяти и протоколах
- Base64Запись двоичных данных 64 печатными символами: алфавит, группы по 3 байта, дополнение «=», base64url
- MQTTПротокол издатель — подписчик через брокер: топики, QoS, retain, Last Will
- Реализация протокола обменаКадры по паузе, длине и разделителю, COBS, CRC-16, таймауты, разбор потока