fk0, легенда (05.02.2020 23:34, просмотров: 692) ответил Evgeny_CD на HDLC может неожиданно дать оверхед 2х -> если данные "неудачные". ASN.1 сложно для совсем мелких контроллеров.
А у тебя какие-то новые предложения по реализации escape-последовательностей? Я могу только предложить от них отказаться, путём отказа от передачи произвольных бинарных данных (кому надо -- пусть в base64 кодируют). Тогда любые числа (целые, плавающие, знаковые, беззнаковые -- в LEB128), строки (без символа с кодом 0) вполне кодируются. А символ с кодом 0 может служить для разделения строк/записей/кадров. Соответственно, включив приём данных в последовательном канале в произвольный момент, отбрасываем все данные до первого нуля, потом начинаем декодировать. Но это уровень "канала" в твоём понимании. То есть ещё раз и более детально мои тезисы:
ОБЩАЯ СТРУКТУРА ПОТОКА ДАННЫХ:
1) работа ведётся с 8-битным потоком данных;
2) данные представлены записями/строками/пакетами/кадрами конечной длины (чтоб в память помещались);
3) имеется возможность поиска в потоке начала пакета по маркеру (символ с кодом 0);
4) маркер встречается как перед началом (см. ниже пункт 6), так и непосредственно в конце пакета;
5) каждый пакет состоит из полей -- последовательности закодированных чисел или строк (см. ниже), данные закодированы таким образом, что маркер разделяющий поля не встречается в данных;
6) после маркера завершающего предыдущий пакет передаётся контрольный циклический код вычисленный побайтово для всех данных переданного пакета -- запись CRC занимает всегда фиксированное число (например 5) байт и кодируется согласно правилам кодирования чисел (см. ниже) и не может включать в себя символа с кодом 0 (разделитель пакетов);
7) самое первое поле с начала пакета используется для маршрутизации (в сети с несколькими устройствами);
8) следующее поле или поля используется для разделения потока на логические каналы;
9) остаток пакета откусывается и передаётся на обработку на более верхний уровень, где оставшиеся поля парсятся окончательно и передаются на прикладной уровень (скорей парсинг происходит "на лету", по мере вычитывания данных;
10) состав полей пакета (количество полей, их тип) полностью определается прикладным уровнем -- в данном случае система передачи отличается от printf/scanf только тем, что данные закодированы в бинарном виде, а не текстом, есть CRC и разделение на логические каналы;
11) допустимо не точное соответствие машинных данных при выводе и при парсинге (выводиться может int_16t, и без ошибок парситься uint8_t если числе в требуемый тип помещается -- это актуально, т.к. вывод и парсинг могут осуществляться на машинах с разной архитуктурой).
КОДИРОВАНИЕ ПОЛЕЙ ДАННЫХ:
1) закодированы могут быть только целые числа (независимо от машинного представления и знака, любого формата от bool до int64_t и uint64_t), числа с плавающей точкой и строки не содержащие символа с кодом ноль, так же могут быть закодированы определяемые пользователем токены (атомы);
2) система кодирования оперирует только двумя типами данных: целое знаковое число (произвольной разрядности, не меньшей чем ~70 бит) и последовательность байтов не содержащая байта с кодом 0;
3) все числа и заданные пользовательские токены отображаются на какие-либо разные диапазоны в пространстве чисел и затем кодируются как число;
4) числа с плавающей точкой конвертируются в IEEE754 (если машинное представление отлично от этого формата) и кодируются как целые в форматах uint32_t или uint64_t (путём отображения на соответствующий диапазон целых чисел, отличный от диапазона выделенного для целочисленных значений). Числа типа long double (80-битные) кодируются как 64-битные.
5) строки кодируются парой записей: число, несущее информацию о длине строки, и последующее содержимое строки без кодирования, для кодирования длин строк в пространстве чисел выделен отдельный диапазон значений;
6) числа переводятся в беззнаковый вид (с помощью алгоритма Зигзаг, например) и кодируются в LEB128 -- таким образом небольшие по абсолютной величине числа кодируются меньшим числом байт, числа с плавающей точкой и большие целые могут занимать в бинарной записи на один-два байта больше, чем в незакодированном виде;
7) описанная система кодирования архитектурно-независимая -- возможен обмен данных между вычислительными машинами разных архитектур.
ДОПОЛНИТЕЛЬНО (что востребовано в приложениях оперирующих сложными наборами данных) вводится слой структурированного кодирования данных. Который позволяет парсеру разбирать наборы данных с не фиксированной структурой или составом данных, не предусмотренными в момент создания парсера (приближение к ASN.1). При этом принимаются следующие тезисы:
1) кодирование производится в рамках последовательности полей состоящих из пользовательских токенов (атомов), чисел и строк описанной выше;
2) пользовательские токены используюся для определения различимых (с разными номерами) типов данных и их длин (если какой-то тип подразумевает различные длины записи, то для кодирования длины используется диапазон значений токенов);
3) при кодировании вначале следует токен, определяющий тип и длину записи, затем последовательность чисел или строк содержащая данные записи, таким образом парсер может во-первых пропускать неизвестные записи и корректно продолжать парсинг дальше, во-вторых определять тип следующей разбираемой записи.
В рамках описанной выше системы можно построение высокоэффективного кодера на C++ (когда кодирование выполняется по большей части во время компиляции и во время исполнения оканчивается десятком машинных команд для формирования нескольких байт записи). Реализация декодера на C++ эффективней scanf конечно, но не слишком блещет: декодирование возможно на лету (не нужна аллокация памяти для декодированных данных), декодер относительно большой (не инлайнится эффективно) и содержит циклы (разбор поля побайтово, switch-case для выбора варианта, проверка того, что выбранный вариант конвертируется в запрошенный и формирование ошибки если нет).
[ZX]