Разбор двоичных данных в C?

Существуют ли библиотеки или руководства для чтения и анализа двоичных данных в C?

Я рассматриваю некоторые функции, которые будут получать пакеты TCP в сетевом сокете и затем анализировать эти двоичные данные в соответствии со спецификацией, превращая информацию в более полезную форму кодом.

Существуют ли там библиотеки, которые делают это, или даже праймер для выполнения этого типа вещей?

Стандартный способ сделать это в C / C ++ действительно отличает структуры как «gwaredd»

Это не так опасно, как можно было бы подумать. Сначала вы отправляете структуру, которую вы ожидали, как в своем примере, тогда вы проверяете эту структуру на достоверность. Вы должны проверить значения max / min, последовательности завершения и т. Д.

На какой платформе вы находитесь, вы должны прочитать « Сетевое программирование Unix», том 1: «Сетевой интерфейс сокетов» . Купите, возьмите его, украдите (жертва поймет, это как воровать пищу или что-то еще …), но прочитайте ее.

Прочитав Стивенса, большая часть этого будет иметь больше смысла.

Я должен не согласиться со многими ответами здесь. Я настоятельно рекомендую вам избежать соблазна бросить структуру на входящие данные. Это кажется убедительным и может даже работать над вашей текущей целью, но если код когда-либо портируется на другой целевой / окружение / компилятор, у вас возникнут проблемы. Несколько причин:

Endianness : Архитектура, которую вы используете сейчас, может быть большой, но ваша следующая цель может быть малозначной. Или наоборот. Вы можете преодолеть это с помощью макросов (например, ntoh и hton), но это дополнительная работа, и вы всегда будете называть эти macros каждый раз, когда вы ссылаетесь на это поле.

Выравнивание : используемая архитектура может быть способна загружать mutli-байтовое слово при смещении с нечетным адресом, но многие архитектуры не могут. Если 4-байтовое слово располагается на границе 4-байтного выравнивания, нагрузка может вытащить мусор. Даже если сам протокол не имеет несогласованных слов, иногда сам байтовый stream смещается. (Например, хотя определение заголовка IP помещает все 4-байтные слова в 4-байтовые границы, часто заголовок ethernet нажимает сам IP-заголовок на 2-байтовую границу.)

Заполнение : ваш компилятор может выбрать плотную упаковку вашей структуры без прокладки, или она может вставлять прокладку для устранения ограничений выравнивания цели. Я видел это изменение между двумя версиями одного и того же компилятора. Вы можете использовать #pragmas, чтобы устранить проблему, но #pragmas, конечно, специфичны для компилятора.

Бит-упорядочение : упорядочение бит внутри битовых полей С является специфичным для компилятора. Кроме того, биты трудно «получить» для вашего кода времени выполнения. Каждый раз, когда вы ссылаетесь на битовое поле внутри структуры, компилятор должен использовать набор операций маски / сдвига. Конечно, вам придется сделать это маскирование / смещение в какой-то момент, но лучше не делать это при каждой ссылке, если скорость вызывает беспокойство. (Если пространство является главной задачей, то используйте бит-поля, но осторожно пройдите.)

Все это не означает «не использовать структуры». Мой любимый подход состоит в том, чтобы объявить дружественную конструкцию из всех соответствующих протокольных данных без каких-либо битовых полей и не беспокоиться о проблемах, а затем написать набор симметричных подпрограмм пакета / parsingа, которые используют структуру как промежуточную.

typedef struct _MyProtocolData { Bool myBitA; // Using a "Bool" type wastes a lot of space, but it's fast. Bool myBitB; Word32 myWord; // You have a list of base types like Word32, right? } MyProtocolData; Void myProtocolParse(const Byte *pProtocol, MyProtocolData *pData) { // Somewhere, your code has to pick out the bits. Best to just do it one place. pData->myBitA = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_A_MASK >> MY_BIT_A_SHIFT; pData->myBitB = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_B_MASK >> MY_BIT_B_SHIFT; // Endianness and Alignment issues go away when you fetch byte-at-a-time. // Here, I'm assuming the protocol is big-endian. // You could also write a library of "word fetchers" for different sizes and endiannesses. pData->myWord = *(pProtocol + MY_WORD_OFFSET + 0) << 24; pData->myWord += *(pProtocol + MY_WORD_OFFSET + 1) << 16; pData->myWord += *(pProtocol + MY_WORD_OFFSET + 2) << 8; pData->myWord += *(pProtocol + MY_WORD_OFFSET + 3); // You could return something useful, like the end of the protocol or an error code. } Void myProtocolPack(const MyProtocolData *pData, Byte *pProtocol) { // Exercise for the reader! :) } 

Теперь остальная часть вашего кода просто управляет данными внутри дружественных быстрых структурных объектов и только вызывает пакет / синтаксический анализ, когда вам нужно взаимодействовать с streamом байтов. Нет необходимости в ntoh или hton, и никакие битовые поля не замедляют ваш код.

Позвольте мне повторить ваш вопрос, чтобы понять, правильно ли я понял. Вы ищете программное обеспечение, которое примет формальное описание пакета, а затем создаст «декодер» для анализа таких пакетов?

Если это так, ссылка в этом поле – PADS . Хорошей статьей, представляющей это, является PADS: язык, специфичный для домена для обработки специальных данных . PADS является очень полным, но, к сожалению, по несвободной лицензии.

Возможны альтернативы (я не упомянул не-C решения). По-видимому, никто не может считаться полностью готовым к производству:

  • binpac
  • PacketTypes
  • DataScript

Если вы читаете французский язык, я резюмировал эти вопросы в формате Génération de décodeurs de formats binaires .

По моему опыту, лучший способ – сначала написать набор примитивов, чтобы прочитать / записать одно значение какого-либо типа из двоичного буфера. Это дает вам высокую наглядность и очень простой способ справиться с любыми проблемами: просто сделайте все правильно.

Затем вы можете, например, определить struct s для каждого из ваших сообщений протокола, а также написать пакет / распаковать (некоторые называют их serialize / deserialize) для каждого.

В качестве базового случая примитив для извлечения одного 8-битного целого может выглядеть так (при условии, что 8-разрядный char на главной машине, вы можете добавить слой пользовательских типов, чтобы убедиться, что тоже, если это необходимо):

 const void * read_uint8(const void *buffer, unsigned char *value) { const unsigned char *vptr = buffer; *value = *buffer++; return buffer; } 

Здесь я решил вернуть значение по ссылке и вернуть обновленный указатель. Это вопрос вкуса, вы можете, конечно, вернуть значение и обновить указатель по ссылке. Важнейшей частью конструкции является то, что функция чтения обновляет указатель, чтобы сделать эту цепочку.

Теперь мы можем написать аналогичную функцию для чтения 16-разрядной величины без знака:

 const void * read_uint16(const void *buffer, unsigned short *value) { unsigned char lo, hi; buffer = read_uint8(buffer, &hi); buffer = read_uint8(buffer, &lo); *value = (hi << 8) | lo; return buffer; } 

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

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

Возможно, вас заинтересуют буферы протокола Google , которые в основном представляют собой структуру сериализации. Это прежде всего для C ++ / Java / Python (это языки, поддерживаемые Google), но предпринимаются постоянные усилия по переносу на другие языки, включая C. (Я вообще не использовал порт C, но я отвечаю за один из портов C #.)

Вам не нужно разбирать двоичные данные на C, просто нарисуйте указатель на все, что вы думаете.

 struct SomeDataFormat { .... } SomeDataFormat* pParsedData = (SomeDataFormat*) pBuffer; 

Просто будьте осторожны с вопросами о endian, размерами типоразмеров, чтением конца буферов и т. Д. И т. Д.

Разбор / форматирование двоичных структур – одна из немногих вещей, которые легче сделать в C, чем на языках более высокого уровня / управляемых. Вы просто определяете структуру, которая соответствует формату, который вы хотите обработать, а struct это синтаксический анализатор / форматирование. Это работает, потому что структура на C представляет собой точный макет памяти (который, конечно, уже двоичный). См. Также ответы kervin и gwaredd.

Я не совсем понимаю, какую библиотеку вы ищете? Общая библиотека, которая будет принимать любой двоичный вход и будет анализировать его в неизвестном формате? Я не уверен, что такая библиотека может существовать на любом языке. Я думаю, вам нужно немного уточнить свой вопрос.

Изменить :
Хорошо, поэтому, прочитав ответ Джона, кажется, что есть библиотека, ну вроде библиотеки, это больше похоже на инструмент генерации кода. Но, как многие заявляли, что они просто передают данные в соответствующую структуру данных, с надлежащей тщательностью, т. Е. С использованием упакованных структур и заботой о конечных проблемах, вы хорошо себя чувствуете. Используя такой инструмент с C, это просто избыток.

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

Чтобы разобраться с порядковыми байтами, были введены сетевые порядки байтов – обычная практика заключается в том, чтобы преобразовать числа от байта хоста к сетевому порядку байта перед отправкой данных и конвертировать обратно к порядку хоста при получении. См. Функции htonl , htons , ntohl и ntohs .

И действительно подумайте о совете Кервина – прочитайте UNP . Вы не пожалеете!