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

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

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

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


person kaybenleroll    schedule 26.11.2008    source источник


Ответы (9)


Стандартный способ сделать это в C / C ++ - это преобразование в структуры, как предложил gwaredd.

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

На какой бы платформе вы ни работали, вы должны прочитать Сетевое программирование Unix, том 1. Сетевой API для сокетов. Купите, одолжите, украдите (жертва поймет, это как воровать еду или что-то в этом роде ...), но прочтите это.

После прочтения Стивенса большая часть этого станет более понятной.

person kervin    schedule 26.11.2008
comment
Я скептически отношусь к методу cast тогда проверяю. Если не проверить, вы рискуете получить неверные данные. А если проверить, в чем смысл кастинга? Проверка будет такой же медленной, как и традиционный парсинг. - person bortzmeyer; 27.11.2008
comment
Как писал ниже Кейси Баркер, все не так просто. Вы можете исправить выравнивание и заполнение байтов большую часть времени (и вам нужно знать об этом и тщательно тестировать его с каждой новой системой), но как только вы столкнетесь с проблемами порядка байтов, вы будете вынуждены исправлять каждую структуру индивидуально перед проверкой для действительности. И если вы проверяете валидность, то можете проверить и во время синтаксического анализа. Анализ отдельных токенов также позволяет детально создавать подклассы и версии. - person Groo; 25.02.2011
comment
Действительно, проверка файлов Office, представленная в Office 2010 и позже перенесенная в Office 2007 и Office 2003, в основном проверяет файл на достоверность, чтобы предотвратить использование уязвимостей. - person Yuhong Bao; 07.08.2011
comment
Ссылка на микшер не работает с 2011 года. Похоже, здесь есть действующая копия: csis.bits-pilani.ac.in/faculty/dk_tyagi/Study_stuffs/raw.html - person tiktok; 22.07.2015

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

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

Выравнивание. Используемая вами архитектура может быть способна загружать многобайтовое слово со смещением с нечетным адресом, но многие архитектуры не могут. Если 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'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!  :)
}

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

person Casey Barker    schedule 27.11.2008
comment
Работает ли этот код даже для передачи структуры через сокеты? ››› - person codingfreak; 20.10.2009
comment
Это особенно хорошо для сокетов - особенно когда вы не хотите делать утверждения о порядке байтов / ширины шины / выравнивания процессов на любом конце сокета. - person Casey Barker; 03.11.2009
comment
Я полностью согласен с вашими комментариями, но сам код должен был быть более ясным по этому поводу. Часть, в которой вы конвертируете необработанные байты в слово, должна выполняться с использованием экземпляра некоторого конвертера Endian, чтобы его можно было легко переключить с другой реализацией при необходимости. - person Groo; 25.02.2011
comment
Отсюда мой комментарий к коду. Вы также можете написать библиотеку сборщиков слов для разных размеров и порядка байтов. Так что, вы хотите, чтобы я написал всю библиотеку? :) Но этот код работает на любом ядре с любой конечной ориентацией. Он полностью переносимый, если вы правильно набираете Byte и Word32, поэтому я не понимаю, зачем вам нужно менять реализацию. - person Casey Barker; 18.05.2011
comment
Этот вопрос снова возник недавно, и, перечитав его, я думаю, что Groo упустил суть этой реализации. Итак, я хочу сказать более конкретно: этот код работает для ЛЮБОГО процессора ЛЮБОЙ ширины или порядка байтов, пока ПРОТОКОЛ имеет обратный порядок байтов. Протоколы обычно не меняются после того, как они определены, так что на самом деле это не проблема. - person Casey Barker; 24.08.2011

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

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

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

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

person bortzmeyer    schedule 27.11.2008
comment
@bortzmeyer Это все новости для меня. Спасибо за информацию! - person Bklyn; 13.04.2009

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

Затем вы можете, например, определить structs для каждого из ваших протокольных сообщений и написать функции pack / unpack (некоторые люди называют их сериализацией / десериализацией) для каждого.

В качестве базового случая примитив для извлечения одного 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;
}

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

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

person unwind    schedule 27.11.2008
comment
Вы имеете в виду vptr ++, не так ли, также отсутствует приведение буфера к char. - person mloskot; 17.06.2013

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

person Jon Skeet    schedule 26.11.2008
comment
Есть много способов сериализации данных (буферы протокола - это хорошо, но это всего лишь один из них, есть также XML, JSON, ASN / 1 + BER и т. Д.). Они работают, только если вы контролируете спецификацию протокола. Если это не так, ваш метод не работает. - person bortzmeyer; 27.11.2008
comment
Абсолютно. Если вы не контролируете протокол, вам придется делать это вручную. - person Jon Skeet; 27.11.2008

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

struct SomeDataFormat
{
    ....
}

SomeDataFormat* pParsedData = (SomeDataFormat*) pBuffer;

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

person Gwaredd    schedule 26.11.2008
comment
Или разные компиляторы и т. Д. Это действительно хрупкий код, ИМО. - person Jon Skeet; 26.11.2008
comment
Согласованный. Я думаю, что все многие и т. Д. - вот почему он хочет, чтобы для этого была библиотека. - person Jonathan Adelson; 26.11.2008
comment
да -... хотя такой подход является разумным, пока вы работаете на одной машине, этого в сетевом программировании действительно следует избегать. - person Johannes Schaub - litb; 26.11.2008
comment
структура данных должна быть определена как упакованная с обеих сторон, и после того, как вы позаботитесь о проблемах с порядком байтов, вы в полной безопасности. - person Ilya; 26.11.2008
comment
По крайней мере, в некоторых случаях нужные вам структуры уже определены - и локально правильно - где-то в наборе заголовков API. Это обычное дело для служб ОС и т. Д. - person dmckee --- ex-moderator kitten; 26.11.2008
comment
Если вы должны написать это самостоятельно, посмотрите, какую директиву # pragma предоставляет ваш компилятор, чтобы убедиться, что вы правильно поняли упаковку и порядок байтов. - person dmckee --- ex-moderator kitten; 26.11.2008
comment
Чрезвычайно опасный метод при чтении пакетов в сети: злоумышленнику будет очень легко отправить вам совершенно неверные данные. - person bortzmeyer; 27.11.2008
comment
Я использовал это в нескольких проектах, и у меня никогда не было проблем. Вам нужно следить за проблемами упаковки и выравнивания - легко исправить с помощью прагмы. Порядок байтов и недопустимые / опасные данные, которые вам нужно проверить, независимо от того, какой метод вы используете. Сериализация (а) медленнее (б) по сравнению с инженерным imo (в большинстве случаев) - person Gwaredd; 27.11.2008
comment
Пожалуйста, не просто создавайте структуры, они ОЧЕНЬ хрупкие, см. Ответ Кейси Баркера, чтобы узнать, как это правильно сделать. - person kalleh; 13.12.2009

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

person Matt Campbell    schedule 26.11.2008

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

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

person Ilya    schedule 26.11.2008

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

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

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

person qrdl    schedule 27.11.2008