Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

typedef struct _MyProtocolData
{
    Bool myBitA;  // Using a "Bool" type wastes a lot of space, but it 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!  :)
}

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

Ответ 3

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

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

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

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

Ответ 4

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

Затем вы можете, например, определить struct для каждого из ваших протокольных сообщений, а также написать пакет/распаковать (некоторые из них называют их 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;
}

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

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

Ответ 5

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

Ответ 6

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

struct SomeDataFormat
{
    ....
}

SomeDataFormat* pParsedData = (SomeDataFormat*) pBuffer;

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

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