В один прекрасный момент у меня встала задача сжатия пакетов в условиях спутниковой связи, соответственно tun интерфейс и нестабильная работа Rohs. Написал свое видение решения подобной проблемы. К сожалению код прилагать не могу, но подробно опишу логику сего процесса.
План
Получил проблему
Решил проблему
Счастье)
А если серьезно, то задача была получить пакет, максимально сжать его, сохранить референс, и собрать заново, а на целевом устройстве, все эти действия в обратном порядке.
Логика
Нам поступает пакет, это 20 байт заголовка (рассмотрим только l2 заголовки, к l3 вернемся в следующих публикациях) и какое то количество данных, допустим 40 байт. Так вот, заголовок не обязательно сохранять полностью, ведь он будет нужен только на целевом устройстве, а не в процессе пути, значит его можно сократить.
За этим встал вопрос как. После изучения информации, пришло осознание что самый логичный вариант это метод избыточности, т.е. когда отбрасываются идентичные для группы заголовков данные. Для общего понимания чем мы будем заниматься держите краткий экскурс по содержанию заголовка.

Когда мы преисполнились в познании структуры заголовков, можем перейти к сути. К примеру у нас есть два устройства, модем M и спутник S, такие данные как версия, занимают солидное по меркам пакета количество памяти, а мы и так знаем что ничего помимо пакетов ipv4 у нас по интерфейсу летать не будет. Эти данные можно со спокойной душой отбросить, и сделать чем то наподобие константы. Для этого и существуют референсы, как бы эталонные заголовки для восстановления байтов которые были сжаты на устройстве отправления. Учтем что устройства у нас два, а значит что для начала нужно сообщить декомпрессору на той стороне какой заголовок нужно сделать эталонным. И вот так плавно мы переходим к реализации.
Скрытый текст
Псс, поправки всегда приветствуются)
Реализация
Начнем с короткого плана, который позволит разделить работу на этапы.
Разделение заголовка и данных
Сжатие заголовка с сохранением эталона
Формирование сжатого пакета
Разделение сжатого заголовка и данных на целевом устройстве
Декомпрессия пакета на целевом устройстве с сохранением эталона
Формирование исходного пакета на целевом устройстве
А теперь по порядку.
Разделение заголовка и данных
Тут на самом деле все просто, когда у нас есть целый заголовок, мы можем просто взять первые 20 байт пакета или поступить более профессионально. Специально для этого в заголовке существует поле Header Length. Например, значение 5 в этом поле означает, что длина заголовка составляет 20 байт (5 * 4), а значение 15 - 60 байт.
Сжатие заголовка с сохранением эталона
Первый отправленный пакет всегда должен быть полным, так как декомпрессор на целевом устройстве еще не знает как именно ему разжимать пакет и какие данные добавлять. Для этого разделим компрессор на 2 части, сжатие с эталоном и сжатие без эталона. Эталонные заголовки записываются в контейнер, условный std::vector, и если он пуст, то запускается первый метод, а если до этого был сохранен заголовок, то второй.
Для сжатия первым методом, нам нужно только добавить один байт идентификатор в самое начало заголовка, чтобы декомпрессор понимал, что заголовок не был сжат, и отбросив первый байт, сохранил себе эталон, с помощью которого он в дальнейшем будет восстанавливать сжатые заголовки.
// Исходный пакет (4 байта данных)
uint8_t originalPacket[24] = {0x45, 0x00,
0x00, 0x3c,
0xab, 0xcd,
0x40, 0x00,
0x40, 0x06,
0x00, 0x00,
0xc0, 0xa8, 0x00, 0x01,
0xc0, 0xa8, 0x00, 0x02,0x40,0x40,0x40,0x40};
//Вывод
//0x000 0x045 0x000 0x000 0x03c 0x0ab 0x0cd 0x040 0x000 0x040 0x006 0x000 0x000 0x0c0 0x0a8 0x000 0x001 0x0c0 0x0a8 0x000 0x002 0x040 0x040 0x040 0x040
Второй метод сжатия подразумевает расчет дельты между эталонным заголовком и текущим заголовком пакета. В сжатый пакет сохранятся только байты которые отличаются от эталона, и 3 сервисных байта. Первый байт — идентификатор метода сжатия, второй байт — идентификатор эталона, который был использован, третий байт — дельта, который так же является размером сжатого заголовка.
// Второй исходный пакет
uint8_t originalPacket2[24] = {0x45, 0x00,
0x00, 0x3c,
0xab, 0xcd,
0x30, 0x00,
0x30, 0x05,
0x00, 0x00,
0xc0, 0xa7, 0x00, 0x01,
0xc0, 0xa8, 0x01, 0x02,0x40,0x40,0x40,0x40};
//Вывод
//0x001 0x000 0x006 0x030 0x030 0x005 0x00d 0x0a7 0x001 0x040 0x040 0x040 0x040
Данные у нас не изменились, это важно! Однако пакет «похудел» на 11 байт, больше чем на 50 процентов, отличная эффективность. Остается только добавить к заголовку заранее сохраненные данные, и пакет готов к отправке.
Разделение сжатого заголовка и данных на целевом устройстве
Как работает декомпрессия в случае первого варианта сжатия, я уже описал выше, тут разберем вариант когда данные заголовка были полноценно сжаты. К нам приходит пакет. По первому байту декомпрессор определит что это второй тип сжатия, из второго байта получит id эталона, а из третьего какого размера сжатый компрессором заголовок. Теперь нам нужно просто отбросить первые 3 сервисных байта, и взять следующие байты заголовка в количестве равном дельте. На выходе мы получим готовый для обработки заголовок.
Декомпрессия пакета на целевом устройстве с сохранением эталона
После получения сжатого заголовка, нам нужно сравнить его с эталоном, идентификатор которого мы получили в предыдущем шаге и вставить сжатые байты на законные места. На данном этапе я настоятельно рекомендую прописать обработку исключений, т.к. баги еще никто не отменял
// Оригинальный пакет
uint8_t originalPacket2[24] = {0x45, 0x00, // Version, IHL, DSCP, ECN
0x00, 0x3c, // Total Length
0xab, 0xcd, // Identification
0x30, 0x00, // Flags, Fragment Offset
0x30, 0x05, // TTL, Protocol (TCP)
0x00, 0x00, // Header Checksum
0xc0, 0xa7, 0x00, 0x01, // Source IP
0xc0, 0xa8, 0x01, 0x02,0x40,0x40,0x40,0x40};
//Вывод
//0x045 0x000 0x000 0x03c 0x0ab 0x0cd 0x030 0x000 0x030 0x005 0x000 0x000 0x0c0 0x0a7 0x000 0x001 0x0c0 0x0a8 0x001 0x002 0x040 0x040 0x040 0x040
Как мы видим, пакет полностью восстановился. Новый эталон сохраняется в пул, и процесс повторяется до бесконечности (ну или почти). Финальным аккордом станет добавление данных к заголовку и отправление целого пакета на обработку.
Как итог мы получаем стабильную библиотеку без лишних усложнений, которую сможем свободно использовать в своих целях. Всем огромное спасибо за внимание, и жду ваших комментариев под постом.