Разбор двоичных данных в C? - PullRequest
19 голосов
/ 26 ноября 2008

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

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

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

Ответы [ 9 ]

29 голосов
/ 27 ноября 2008

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

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

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

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

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

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

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, и нет битовых полей для замедления вашего кода.

14 голосов
/ 26 ноября 2008

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

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

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

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

12 голосов
/ 27 ноября 2008

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

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

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

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

10 голосов
/ 27 ноября 2008

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

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

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

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

3 голосов
/ 26 ноября 2008

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

struct SomeDataFormat
{
    ....
}

SomeDataFormat* pParsedData = (SomeDataFormat*) pBuffer;

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

2 голосов
/ 26 ноября 2008

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

1 голос
/ 27 ноября 2008

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

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

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

1 голос
/ 26 ноября 2008

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

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

...