Обновить

Комментарии 9

Да, выглядит очень и очень интересно. Честно говоря, подумываю для логирования его применить. Как альтернатива KEKS не пойдёт: мало типов данных (это не недостаток, если что, а тоже плюс), всё же наличие экранирования (если мы передаём бинарь, то в нём newline-ы могут встречаться -- Tree закодировать сможет, спору нет, но в памяти кусок каких-нибудь 32-байт всё же запросто может иметь "\" вставки, а значит мы вынуждены будем сохранять unescaped/raw копию декодированного значения).

Как альтернатива KEKS подойдёт не сам формат, а язык на его базе, по аналогии с json.tree, например. Декодированное значение можно собрать и налету из кусков, но да просто сослаться на кусок исходного буфера без абстракций сверху не получится.

Практически все современные процессоры little endian. Зачем кодировать целочисленные в big endian?

Удобство для разработчика (удобство вещь субъективная -- нам так удобнее). Ну и преобразование между endianness настолько дешёвое, что про это мы не думаем. Не было надобности в кодеке работающим на безумных скоростях типа cap'n'proto, flatbuffers и подобных.

У Cap'n'proto есть другая интересная штука для производительности - http://capnproto.org/rpc.html в виде сокращения числа раунд-трипов (точнее для убирания не-производительности). Для описанного в комменте ниже, когда у нас latency может достигать скажем секунд, это серьезно. Но у них только промисы в виде одной переменной, а я предполагаю решать проблему передачей на сервер скрипта, который он исполнит, выполнив таким образом несколько API-запросов за раз (подразумевается if/else и т.п. по данным, содержащимся исключительно в ответах, а не привязанных к клиенту). Это одна из целей, для которой делается CBOR-TPL, и поскольку он "бинарный", фронтенд-языки будут называться... ну скажем CBOR-TCL :)

На связи участник IETF CBOR WG, пришедший сюда из гугля по "Tcl CBOR" для ответа на сообщение в mail list насчет 'Hopefully nobody is treating "2" and 2 as the same', происходящего в рамках обсуждений по теме детерминированного CBOR, длящихся уже несколько лет (да, как бы что кому не казалось, детерминизм на самом деле сложная тема).

Но для начала необходимо озвучить несколько философско-мировоззренческих постулатов.

Аксиома первая - для прогресса человечества как наивысшей ценности первейшим врагом является нежелание думать, включать мозг; в контексте обсуждения это прежде всего значит чрезмерное упрощение и желание быстро что-нибудь склепать вместо стратегического подхода с предвидением будущего. Да, это естественно и биологически обусловленно, как нам постоянно рассказывает проф. С.В.Савельев, потому что мозг потребляет до 20% энергии тела при массе в 2%, и тем не менее для людей третичного сознания (по нему же), т.е. творящих что-то новое = двигающих прогресс, это недопустимо. А изобретение форматов относится именно сюда.

И ключевым является именно not simpler, которое обычно и нарушают; например типичным является, когда из-за черзмерной простоты потом в реальном мире приходится городить костыли, и в итоге всё оказывается сложнее, чем если бы было предусмотрено изначально.

Примеров в жизни тьма-тьмущая, например какой-нибудь suckless, который на поверку оказывается уровня от sucks до полной неюзабельности - потому что переупростили, повыкидывав нужное.

Постулат второй (частично следствие предыдущего): раздувание (bloat) и ориентация на "производительность" является врагом, и в том числе моральным - для развития необходимо наличие ограничений (негры в теплой сытой Африке как-то не очень развили науку и технику, в отличие от вынужденных преодолевать климатические ограничения северян и для этого что-то придумывать). Примером является тенденция развития IT-отрасли и около с начала 90-х - закон Мура и рост производительности сыграли развращающе, не только программисты прекратили оптимизировать код, что современный софт на современном железе работает медленнее, чем четверть века назад тогдашний софт на тогдашнем железе, но и обычные люди стали звонить голосом или как минимум строчить в реалтаймовых мессенджерах, не стремясь подумать и написать развернуто письмо (вот в Фидо было отлично!), или смотрящие видео только онлайн, будуче не в состоянии потерпеть и десять минут для скачки фильма торрентом (в наше время и час проблемой не был!). "Производительность" и блоат на руку только корпорациям, поскольку нужны в централизованной модели "наш ДЦ обрабатывает как можно больше пользователей".
К счастью, эпоха дешевой нефти и закона Мура заканчивается - пора забыть о производительности и думать наконец о децентрализации.

Объединяя 1 и 2, мы будем как примеры видеть системы, стремящиеся к строгости и ортогональности (в идеале как в математике), но при этом не закрытые и ригидные, а открытые и расширяемые - ну, скажем, подход "маленькое ядро и плагины" сюда.
Сюда стоило бы добавить ЧСВ, ну или не знаю, по какой причине программисты так любят переизобретать велосипеды, например свои форматы сериализации. При этом мало сделать формат, нужно сделать его пригодным для очень большого количества реальных задач в реальном мире - и сохранить при этом достаточную строгость и простоту с ортогональностью является целым искусством (пожалуй, в математике тоже, глядя сколько лет в истории поиски стройных систем аксиом и т.п. проводились), например JSON. То есть, если не делать формат для какого-то сугубо очень частного случая, то на самом деле не стоит даже и пытаться, если нет готовности приложить очень много усилий для изучения потребностей реального мира. Забегая вперёд, CBOR удалось в достаточной степени - да, не идеально и не без проблем, но это формат "на пять с минусом", в отличие от велосипедов. А как и в криптографии, лучше брать готовые массовые решения как "строительные блоки", нежели велосипедить.

Постулат третий - поскольку способность делать то или иное из практических задач важнее, чем отказ от них, то криптография - не королева, а служанка. Безопасность есть лишь свойство выполнения той или иной задачи - приоритеты могут быть таковы, что лучше сделать небезопасно, чем отказаться (в OpenBSD делают наоборот, как типичные невротики), но под соусом тотального шифрования (зачем? восстание масс не-публичными e2e-сообщениями не поднять, тут максимум подпись нужна) опять же всё в свои централизованные лапы захватывают корпорации с повальным HTTPS, например (а такие корпорации - враг).

Типичной комбинацией 1 и 3 является крайне типичные предложения гиков о соцсетях и т.п. где идентификатором пользователя будет публичный ключ. Почему в реальном мире это не работает, им мозгов не хватает понять (или включить) - типичное, когда человек в своей области разбирается (криптографии допустим), а в остальном нет.
Сюда я б еще добавил пример из типовой ошибки ориентации на авторитеты - скажем, известный DJBernstein хорош в криптографии, но люди почему-то из-за этого считают его авторитетом во всём, когда это не так. Скажем, код его qmail или DNS просто отвратителен, немайнтенабелен, да кстати и DNSCurve его не взлетел (а очень жаль!) из-за банальной неспособности выпустить спецификацию в виде RFC для независимых имплементаций, а не "берите мой криптобокс".

Прежде чем перейти к статье, на фразу из коммента выше:

мало типов данных (это не недостаток, если что, а тоже плюс)

См. аксиому первую: да, это таки недостаток, потому что когда они понадобятся - а они понадобятся - начнется горождение всяких костылей, весьма вероятно, очень fragile.

Теперь к статье. С частью про ASN.1 полностью согласен, правда непонятно, почему среди альтернатив не рассмотрен OpenSSH. Вообще SSH2 недооценённый протокол несмотря на распространенность, очень хороший и человечный, и у них даже сертификаты есть, кстати! Я думал его брать для своего muSCTP (и проекта уровнем выше, аналога Фидо) в качестве основы крипты, чисто почитав RFC, а потому узнал про signify и asignify и вот теперь еще age тоже, оказывается.

В секции требований почему-то производительность не упоминается вообще, хотя дальше большой упор на неё везде - но тут см. постулат 2. И вроде бы секция требований выглядит разумно, но:

Для криптографических задач критична детерминированность кодека. Единственно верное, каноничное представление данных. Хотя есть и вариант принятый в JSON-based криптографии: просто засовывать JSON внутрь строки другого JSON. Но это жуткий overhead и делается от безысходности и ограниченности web-экосистемы.

А вот и нет, см. аксиому 1. Засовывание подписываемого в отдельную бинарную строку, которая и подписывается - это не безысходность, а в 90% случаев самый правильный вариант. Да, в JSON оверхед, но он из-за текстовости - в любом бинарном формате оверхеда не будет. В COSE = CBOR Object Signing and Encryption применён именно этот подход, в том числе из-за необходимости работы на constrained-устройствах.

Теперь подробнее, почему детерминированность - костыль. Я это изложил в двух письмах в mail list:

Вкратце, от нежелания включать мозг - исходят из ложной посылки "два объекта равны, если равны их хэши, а чтобы сделать равными хэши, закодируем детерминированно". В реальном мире этому противоречит та же потоковость, компрессия, но более всего - есть философская проблема. При этом шаг назад - для чего еще это применять, кроме как не для сравнения объектов? То есть, автор dCBOR даже подтверждал, что главное применение - не для того, чтоб передавать детерминированно, а когда НЕ ПЕРЕДАВАТЬ. Ну, скажем, понимать, что такой объект уже есть в кэше, и сэкономить гигабайты.
Так вот, философская проблема - на самом деле, объекты равны только когда ИНТЕРПРЕТИРУЮТСЯ одинаково. Более того, интерпретируются одинаково ВСЕМИ ИМПЛЕМЕНТАЦИЯМИ - что, понятно, при достаточной сложности приложений (тут не про кодек формата речь совсем, он на OSI Layer 6, а приложение на Layer 7) вообще теоретически недостижимо - как от багов (мало кто из нас Кнут, чтоб суметь доказать математически), так и от невозможности даже получить код всех имплементаций в мире. Ну ладно, это лирика, но такое понятие, использовавшееся последние годы в подготовке драфта Common Determistic Encoding (of CBOR), как ALDR - Application-Layer Determinism Rules - вполне про практику. О чем речь?
Ну вот пример, есть у меня логгер сообщений в Telegram (то есть если кто что удалил или редактировал, у меня на диске останется), который из их кошмарного формата переводит в CBOR. Хочу я из него сделать свой полноценный клиент, да не простой, а с поддержкой p2p-обмена - допустим, у товарища в его стране такой-то канал забанен, у меня нет (или сам он где-то), да или просто сравнивать, как сервер в разные места отдает, не дурят ли нас корпораты... в общем, естественным образом напрашивается обмен сообщениями, но только теми, которых у получателя нет, для чего естественно выглядит обменяться их хэшами. И тут первая же засада - в них частенько бывает поле access_hash, что-то вроде куки/токена доступа к конкретному объекту, для каждого юзера он свой. То есть тупое детерминированное представление и хэш по нему нам ничем не поможет - надо выкидывать поле или из хэширования, или из объекта. Второй вариант с предобработкой выкидыванием первым приходит в голову, но что, если нам НАДО передавать версию с полем? Городить сбоку прилепливание "после расхэширования вставь такие-то поля туда и туда" в произвольном месте вложенных объектов?.. Или всё-таки переосмыслить тему детерминизма?

Иными словами, даже оставаясь в рамках темы "хэши нам помогут", правильным подходом будет не линейной хэширование массива байт, а подход Merkle-tree на уровне - считаем хэш каждой пары ключ-значение в мапе отдельно, хэшом мапы будет хэш отсортированных хэшей пар - т.е. нам становится неважен их порядок; у каждого элемента хэш считается не по его текущему представлению, а по канонизированному (это тоже отдельная тема, см. ниже), со списками аналогично, и т.д. Так можно решить например проблему collations (да, в LDAP страшный ASN.1, но сама тема-то имеет право на существование)ю Да, я уже слышу вопль "производительность!" - НУ И ЧТО? См. постулат 2. На самом деле, если речь о скалярах в памяти, которые редактируются как переменные, финальный подсчет даже может оказаться быстрее, если дерево постоянно поддерживать, нежели чем сериализовать это сначала в буфер.

Если ALDR-тема с полями Telegram показалась далекой от практики, то есть гораздо более простые вопросы на самом деле. Допустим, вот у нас есть тип данных для больших чисел, и есть число обычное - мы их считаем равными, если там то же число, или разными? Ах, у нас нет такого типа? А если в будущем введут, что делать? Не введут? Хорошо, пример гораздо проще - инты и флоаты равными считаем, 2 и 2.0 ? Или разными, у них же разная сериализация? Вот dCBOR выбрал подход "вместо 2.0 должен передаваться инт 2", от чего последовали вопли со стороны тех реализаций, которые не динамические языки, и у них это разные типы.
Вот из-за кучи таких нюансов (в этом году еще в NaN payloads погрузились, еще одна бездна) от конфликтующих имплементоров за несколько лет драфт CDE так и не был принят в качестве RFC, а был недавно сменившимся председателем CBOR WG остановлен (будут пытаться зайти по-другому). И подобное ждёт любого автора нового формата, как только он столкнется с имплементациями кроме своей.

Для встраиваемых систем, для мест, где может быть bare metal без ОС: крайне желательно потоковое кодирование. Без него мы будем вынужденны иметь дело с буферизацией, динамической памятью и куда более сложным кодом из-за этого.

Тут сначала должно быть некое просвещение про IoT. Есть такое радио, IEEE 802.15.4, как вайфай, только очень ограниченное - 250 кбит/с, максимальный размер фрейма 127 байт. Причина - устройство, например умный выключатель на липучке, должно годами работать от батареи, а в основном её жрёт именно трансивер, то есть надо как можно меньше данных (можно даже O(N) или хуже алгоритмы, это не так критично, как размер пакета). Из этих 127 байт при максимальном шифровании канального уровня на L3 остается 81 байт. Дальше на этом работают Zigbee со своими протоколами, или 6LoWPAN, в случае которого "в лоб" IPv6-заголовок отожрал бы 40 байт, UDP еще 8 и на данные осталось бы всего 33 байта. Путем различных ухищрений в типовом случае размер доступного пэйлоада поднимают до 60-70 байт.
Типичные устройства, которые с этим работают, в текущей классификации IETF класс C2, самый роскошный, это когда есть килобайт десять RAM, и скока там флэша, в смысле размер скомпилированного кода тоже роляет. В либе QCBOR код сортировки мапов (для достижения детерминизма) занимает около 1.2 Кб, что достигает более чем половины от размера всего остального (зависит от опций компиляции). Для constrained-имплементаций это очень заметный напряг.

Так вот, CBOR делался именно для таких условий, и "потоковое" в нём есть в виде indefinite-length. Но как можно заметить, главным её врагом является детерминизм - например, что мапу надо сначала отсортировать. Это серьезные затраты. А передать число элементов в мапе или массиве (и обычно даже байт в строке) проблемы обычно не составляет - там типичен хардкод типа add("key1", "val1"); add("key1", val2) с последующим "закрытием" контейнера, что как бы потоково, но как бы и не.
Другим применением потоковости мог бы быть highload-стриминг, когда мы открыли мапу и миллион ключей плюём; хоть такой кейс и сложно представить, он тоже конфликтует с детерминизмом.

Приемлемая/достаточная компактность сериализованных данных. Объективной меры, что считать достаточно компактным — нет. Но если сертификат будет занимать в два раза больше чем X.509 ASN.1 DER версия, то неприятно, ведь мы не должны забывать и про какие-нибудь смарт-карты, где каждый килобайт на счету.

Довольно очевидно, что оно должно быть "лучше JSON", а дальше зависит от задачи. Параметры выше я показал (конечно, они и по несколько сотен байт гоняют, блоками, там протокол CoAP вместо HTTP, но всё равно минимизация важна).

Пожеланием является и достаточное количество возможных типов данных, чтобы можно бы было прозрачно заменить JSON, как это часто делают BSON или MessagePack кодеками. Обязательная дифференциация бинарных строк и человекочитаемых текстовых! Крайне желательная возможность передачи строк более четырёх гибибайт, ибо какой же это general purpose кодек, если в нём нельзя содержимое относительно большого файла разом передать? Желательна родная поддержка datetime объектов: это очень часто используемый тип данных.
[...]
FLOAT передаётся как тэг с двумя KEKS-закодированными INT-ами, содержащими произвольной длины мантиссу и экспоненту (по основанию 2). Не компактно, но этот тип данных редко где встречается в «общих» задачах.

А в моей практике наоборот, даты-время я редко вижу, а вот флоаты куда чаще. Даже в том же IoT они есть (координаты и температуры), хотя есть и места, где с флоатами напряг. Засада в другом - если формат объявляется general purpose, нужна возможность вводить произвольные типы данных, т.е. хотя бы описать расширения. В CBOR это решено тегами.

CBOR, судя по описанию, должен быть идеалом! Всё что нам нужно — упомянуто в его спецификации. Но при пристальном рассмотрении, он полностью отпадает. Это самый переусложнённый формат из всех что встречал (исключая ASN.1).
В нём есть система тэгирования данных, как в ASN.1. Многие реализации её вообще не поддерживали. В MessagePack есть extension поле, но оно в общем случае (почти?) не используется. В CBOR-е использование тэгированных данных встречается сплошь и рядом. Поддержка datetime производится только через тэги. Если реализация CBOR не поддерживает тэги, то и не будет datetime.

Большей чуши про CBOR я еще не слышал! Переусложнён?! Многие реализации не поддерживают теги?!! Простите, это какие же?! Теги - базовая часть формата, их нельзя не поддерживать, иначе это не реализация стандарта, а фиг знает что. При этом простейшая реализация CBOR (например на связных списках, хотя в IoT делают не так) пишется в несколько сот строк кода на Си. Сам пробовал, так что это правда. Причем с поддержкой тегов! Да, они будут просто еще одним enum в union в узле списка (например), так никто и не обязывает generic decoder поддерживать конкретные теги - этим может заниматься приложение, то есть потребитель API либы кодека. Это уже если моему приложению надо, пусть для тега 0 парсит как дату. Либы для скриптовых языков могут автоматически конвертировать такие-то теги в объекты языка (ту же дату), но это удобство, а не необходимость.

В качестве ключей словарей могут выступать почти любые типы данных. Как же должен будет декодироваться словарь с int(1), «1», True и 1.0 ключами? Как это будет выглядеть в Python, Lua, Tcl?

Не "почти", а действительно любые - да-да, ключом может быть даже массив или целая другая мапа! Как он будет выглядеть? Да как и в любом ООП-языке, объект класса Map, у которого есть методы .get() и .set(), и объекты для соответствующих значений. Если в Tcl так не получается ну никак, это всё-таки проблема Tcl, а не формата :) На самом деле, CBOR - как и JSON! - не является чем-то отдельным, а лишь строительный блок (OSI Layer 6, Presentation) для приложения - то есть конкретный протокол на базе CBOR определяет, какие в нём допустимы ключи - допустим, только строки - да и всё остальное, кстати, нужны ж имена, значения... Это абсолютно нормально, пользоваться подмножеством, в том числе для поддержки реализаций, где нативные объекты попроще (хотя, говорят, какой-то протокол в дикой природе с составными ключами уже появился...)

Каноничное представление CBOR по определению не может быть потоковым. Наши два требования — взаимоисключающие для CBOR. Кроме того, не всех устраивают недостаточно строгие правила каноничного представления и придуман ещё и dCBOR, который требует нормализации Unicode символов. Что не очень разумно для embedded систем.

Как я уже показал выше, эти два требования взаимоисключающие по природе, а не в CBOR. И в KEKS будет ровно та же самая проблема - мапу надо отсортировать перед сериализацией, а это не потоково.

Какие реализации CBOR поддерживают строгое декодирование каноничного представления CBOR или dCBOR? Среди нескольких десятков библиотек на Си и Go не нашлось ни одной!

Как я уже рассказал выше, из-за сложности проблемы документ готовился очень долго, а авторы dCBOR решили базировать свой на этом драфте, потому сами находятся в стадии драфта до сих пор, а не RFC (вот недавно его поправили чтоб было без упоминаний, надеюсь теперь будет прогресс). А раз RFC нет, то и авторы библиотек не спешат поддерживать (я тоже хотел реализовать dCBOR, но решил подождать официальной спеки).
Тем не менее, это совершенно не мешало отдельным протоколам на базе CBOR объявлять свои правила для детерминистичного представления, например так сделал Bundle Protocol (см. ниже).

Отдельной проблемой видится желание авторов структур для CBOR экономить на байтах. Вместо плюс-минус человекочитаемых названий ключей, используют целые числа или тэги. Почти старый добрый ASN.1 BER.

Как я уже рассказал выше, это особенность тех CBOR-based протоколов, которые работают вон в тех условиях. На 60 байт особенно не развернетесь, поэтому там предпочитают массивы, а не мапы. Например, [ "lat", "lon" ] даже чисто самими строками занимает 8 байт, а еще заголовок массива. То есть 15% пакета уже сожрано, даже если он не повторяется в нём больше. Это - чрезмерно дохера. Двухбайтовый тег CBOR здесь гораздо, гораздо эффективнее. При этом, понятно, стараются делать такие схемы данных, чтобы избегать даже тегов по возможности.
Но! Это только в сфере constrained-устройств, хотя они и были одной из мотиваций создания CBOR. В "больших" протоколах прекрасно используют человекочитаемые названий ключей, конечно же (Я б тут встречно поехидничал насчет человекочитаемости "a", "v" и "cek", между прочим).

Реальной же проблемой является НЕДОСТАТОЧНОЕ желание авторов [структур для] CBOR экономить на байтах. Тут недостаточно постулатов насчет блоата, щас поясню. Мы вступили в новый мир - забудьте о производительности, теперь самым важным будет являться размер. Это должно быть понятно любому, кто работает на фронте, где врагом является Великий Цензурский Файрвол - я не говорю о простых пользователях трех букв, а о тех, кто придумывает новое в этой области, причем желательно стратегически, а не затыкает текущие дыры. Конкретику предсказать трудно, но ясно, что покуда у нас еще есть онлайн (когда и его не будет, я в сентябре конце прошлого десятилетия на конференции слышал про утилиту NNCP), он будет всё более и более замедляться и/или ограничиваться размером пакетов. Ну, например, при маскировке пакета данных под GET /kotik001.jpg там может быть реальный jpg (допустим прокси нас слушает), в котором несколько десятков байт - наш пакет данных. Или когда еще не настолько жёстко, но наши сорок байт размазаны в полноценном TCP-пакете. То есть приходим к необходимости будущих протоколов работать на примерно тех же размерах, что 802.15.4, но без ограничений по CPU/памяти, и на производительность будет насрать - не до жиру, главное чтоб работало, да и ждали же во времена Фидо и ничего.

Поэтому, замыслив новый гипертекстовый растровый Фидонет, я пока его самого толком не проектировал, а начал с основы - транспортного протокола muSCTP, потому что TCP в таких условиях работать не сможет (а тем более еще более жирный QUIC). Поэтому нужна максимальная экономия байтов и компрессия. Причем с компрессией всё хорошо только на больших данных, RFC 2394 советует на пакетах не менее 90 байт; да и забивать окно компрессора структурами протокола тоже не способствует, лучше увеличить степень сжатия двухфазным подходом. И я реально помучался в попытке сделать более компактный формат, чем CBOR, в итоге решив, что надо не велосипедить с форматом, а просто сделать схему адаптации, чтоб потом, как в криптографии, просто засунуть в любую имеющуюся реализацию CBOR. Кстати о криптографии, на 33-байтных пакетах это тот еще челлендж - все распространенные подходы рассчитаны на оверхед в десятки байт, что не подходит ну никак, блин.

Поэтому я год назад, посмотрев на draft-cbor-packed, ужаснулся неэффективности, и стал доводить свой CBAR до ума и спеки, а дальше расширил его до CBAPT и даже интерпретируемого языка CBOR-TPL, построенного примерно как Tcl, кстати. Подробнее:

MAP кодируется аналогично, но парами key-value значений. Ключ — не пустая строка. Все ключи отсортированы побайтно по возрастанию, с учётом длины (короткие сначала).

Кто из двух будет первым, "abc" или "cd" ? В естественной memcmp-сортировке "abc" будет первым.

для кодирования datetime выделено три тэга: TAI64, TAI64N, TAI64NA. Это предложенный Дэниелем Бернштейном (DJB) формат. 64-бит big-endian количество TAI секунд с 1970-го года, плюс 2**62. «Смещение» значения времени относительно нуля — удобство для того, чтобы время до 1970 можно было указывать без преобразования в отрицательное число.

В этом месте надо поплеваться на DJB ядом, как всегда, за пределами крипты плохое советует. В протоколах, рассчитанных на десятилетия и тем более столетия (а файлы на диске могут лежать в архивах / найтись археологами) очень плохо promote'ить юниксовую эпоху - здесь время должно задаваться хотя б NTP, а лучше в Julian Day, астрономия решает. Но если что, в принятом не так давно CBOR-стандарте [RFC9581] для тега time есть поддержка и TAI с PTP epoch.

BLOB: потоково закодированная бинарная строка:
тэг + 64-бит big-endian chunk-len + BIN(chunk) + ... + BIN(last-chunk).
last-chunk должен иметь меньшую длину чем chunk-len, пускай даже нулевую.
В формате типа ASN.1 CER, который тоже потоковый, все строки насильно разбиваются на кусочки (chunk), что создаёт неудобства для программиста, так как в памяти данные будут расположены не линейно. В KEKS же, чёткое разделение атомарных и потоковых строк. Как правило, мы заранее знаем, где у нас произвольные данные огромного размера могут пойти — там будем применять BLOB.

Непонятно, зачем именно такой формат для блобов всегда, кто такой BIN (наверное у него таки есть своя длина?), ну или на худой конец зачем там сhunk-len аж на 8 байт всегда, если его можно взять просто из первого чанка? А так - ну чисто indefinite-length в CBOR, никакой выгоды.

Дальше идёт про CDDL, и как ни странно, в положительном ключе - видимо, не было опыта применения? Потому что претензию к CBOR можно было бы ожидать как раз здесь - CDDL делали теоретики в башне из слоновой кости для целей формального языка описания протоколов в RFC и генерации примеров по схеме (всяких foo/bar/baz), поэтому для целей валидации входящего пакета, ну точнее вообще для чего делался JSON Schema, оно подходит ну так себе.

Резюмируя по формату: расширения не прописаны, текущий набор типов не строгий/стройный/ортогональный, а какой попало, подвержен тем же проблемам, что и CBOR, в плане детерминизма - они нифига не решены (просто проигнорированы), менее компактный. Смысл существования не оправдан - как и с криптографией, где не следует изобретать свой шифр или хэш из "смотрите, я тоже могу!", так и с сериализацией, лучше взять СТАНДАРТ, для которого уже много десятков имплементаций, есть интероперабельность, экосистема, открытость в плане регистрации новых тегов и т.д., и просто адаптировать под конкретные задачи. На https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml кстати можно посмотреть, даже конкретно по криптографии уже немаленький такой набор спецификаций есть. Есть COSE, RFC 9360 и даже https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/ описывающий X.509 - не то что б я любил X.509, но это то, на что первым делом надо было смотреть в поисках альтернативы ASN.1, ибо какой смысл ссать против ветра, разрабатывая не профиль применения, а прям формат?..

А вот язык проверки схем мне очень понравился! Я бы утащил подход в CBOR-TPL, тем более, надо заметить, этот язык никак не привязан к KEKS - он много к чему подойдет, и к CBOR, и даже подмножеству JSON наверное. У меня в дальнейших планах для пущей компрессии описывать вообще распаковку из сырых байт - ну как сырая Си-структура, но с машиночитаемой инфой по преобразованию её в CBOR, чтоб на приёмной стороне не писать распаковщик; думаю, этот подход можно развить дальше и до такого...

Остальную часть надо было выделить в отдельный пост - я в криптографии разбираюсь плохо (и подобных мне наверняка много), а тем, кто разбирается, скорее всего не интересен формат сериализации - для них описания field было бы достатчно (если вообще не интуитивно понятно, но можно было бы проставить ссылку на другой пост); так что просто проскроллил, особо прокомментировать вряд ли смогу - кроме технических замечаний, что нотации со слэшами толком не описаны...

Тем не менее, это совершенно не мешало отдельным протоколам на базе CBOR объявлять свои правила для детерминистичного представления, например так сделал Bundle Protocol (см. ниже).

Ах да, забытая часть. Есть такая группа среди исследователей и авторов стандартов Интернета, Delay Tolerant Networking. Изначально создавалась для космической связи, когда онлайн вовсе не постоянен. Поэтому в ней вместо TCP применяется например LTP [RFC 5326], а выше уровнем - в качестве пакетов так называемый Bundle (да, примерно в фидошном смысле), который распространяется методом Store&Forward. И вот, в версии 2007 года, RFC 5050, Bundle Protocol использовал собственный способ сериализации, в частности, на базе чисел переменной длины, SDNV (сначала они были как в ASN.1, потом стали неограниченной длины, с признаком в старшем бите конец или нет).

Так вот, в новой версии (почти 4 года назад), они избавились от этого велосипеда и перешли на CBOR. Поскольку единого стандарта для детерминированного представления CBOR на тот момент не было, они просто взяли подходящий слой (как строительный блок) из свежего RFC и доопределили нужное самостоятельно:

4.1. Bundle Structure

The format of bundles SHALL conform to the Concise Binary Object Representation (CBOR) [RFC8949].

Cryptographic verification of a block is possible only if the sequence of octets on which the verifying node computes its hash -- the canonicalized representation of the block -- is identical to the sequence of octets on which the hash declared for that block was computed. To ensure that blocks are always in canonical representation when they are transmitted and received, the CBOR encodings of the values of all fields in all blocks MUST conform to the core deterministic encoding requirements as specified in [RFC8949], except that indefinite-length items are not prohibited. Each bundle SHALL be a concatenated sequence of at least two blocks, represented as a CBOR indefinite-length array. The first block in the sequence (the first item of the array) MUST be a primary bundle block in CBOR encoding as described below; the bundle MUST have exactly one primary bundle block. The primary block MUST be followed by one or more canonical bundle blocks (additional array items) in CBOR encoding as described in Section 4.3.2. Every block following the primary block SHALL be the CBOR encoding of a canonical block. The last such block MUST be a payload block; the bundle MUST have exactly one payload block. The payload block SHALL be followed by a CBOR "break" stop code, terminating the array.

(Note that, while CBOR permits considerable flexibility in the encoding of bundles, this flexibility must not be interpreted as inviting increased complexity in PDU structure.)

Associated with each block of a bundle is a block number. The block number uniquely identifies the block within the bundle, enabling blocks (notably Bundle Protocol Security blocks) to reference other blocks in the same bundle without ambiguity. The block number of the primary block is implicitly zero; the block numbers of all other blocks are explicitly stated in block headers as noted below. Block numbering is unrelated to the order in which blocks are sequenced in the bundle. The block number of the payload block is always 1.

Выделение жирным моё, и оно подтверждает говорившееся мной ранее - каждый протокол на application layer волен самостоятельно ограничивать нужное ему подмножество, после чего CBOR становится вовсе не таким сложным и страшным, как казался. А вот возможность взять готовую и отлаженную реализацию из десятков для любого языка - гораздо ценнее велосипедов, с которым эти шишки придется проходить заново.

В моём проекте PyDERASN имеется строгая проверка DER, но он написан на Python, поэтому ни о каких embedded систем речи не идёт.

Гы, а почему такое название? Помнится, на @freebsd_ru в Телеграме была какая-то проблема с портом, угорали с названия.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации