Мне интересно, есть ли какая-либо известная передовая практика / метод для анализа смешанного типа пакета данных.
Например, предположим, что данные состоят из 10 байт и состоят из:
Байт 0-1: идентификатор производителя (int)
Байт 2: тип (целое)
Байт 3-4: идентификатор устройства (ascii char)
Я мог бы просто определить размер и расположение каждого типа данных как #define и проанализировать его, используя эти определения. Но мне интересно, есть ли какая-нибудь структура, которая могла бы это лучше организовать.
Если вы заранее знаете определение пакета, почему бы просто не определить настраиваемую структуру со всеми необходимыми полями?
Вы должны читать данные точно так же, как вы их написали ... Поскольку похоже, что это двоичный файл ...
Проходят ли данные через файлы для других платформ? Разместите несколько образцов входных данных и экспортированных пакетов выходных данных. Иначе это слишком широко / неясно.
«Байт 0-4» составляет 5 байтов ... и данные должны быть 10 байт ...?
@Ruks, может быть, это очень, очень большие байты * duck-'n-run *
"data is 10 bytes" -> Как определяется расширение 5 байт?
@Jinsuk: «Я мог бы просто определить размер и расположение каждого типа данных как #define и проанализировать его с помощью этих определений» -> Мне искренне жаль, что вы это сделали, опубликовали этот код и затем спросили о лучших практиках. Это добавило бы информации и сделало бы хороший пост. Это слишком широко.





Нравится:
struct packet {
uint16_t mfg;
uint8_t type;
uint16_t devid;
} __attribute__((packed));
Атрибут Packaging (или его эквивалент для вашей платформы) необходим, чтобы избежать неявного заполнения, которого нет в протоколе.
Когда у вас есть указанная выше структура, вы просто приводите (часть) массива char, который вы получили откуда угодно:
char buf[1000];
(struct packet*)(buf + N);
Да. вот так :) ... @ chux, предположительно, если вы знаете определение пакета данных, вы также знаете, какой порядок байтов он использует. Если вам нужно, вы можете поменять местами бит / байт перед преобразованием с помощью структуры.
Кроме того, packed - верный способ вызвать неопределенное поведение даже в тех компиляторах, которые поддерживают упаковку. Приведение массива символов к пакету является нарушением строгого правила псевдонима.
@AnttiHaapala: Я не использовал массив символов. Я использовал buf + N, который является символом *. Который, насколько я понимаю, можно использовать (есть исключение для строгого псевдонима для char *). Что ты об этом думаешь?
Строгий псевдоним для char * - да, но это обратное направление. В нем говорится, что вы можете обращаться к байтам любого объекта как к символьному типу. Это не говорит о том, что вы можете получить доступ к элементам массива символов как к любому типу объекта.
Лучше всего предполагать, что все данные извне программы (например, от пользователя, из файла, из сети, из другого процесса) потенциально неверны (и потенциально небезопасны / вредоносны).
Затем, исходя из предположения о «потенциальной некорректности», определите типы, чтобы различать «непроверенные, потенциально неверные данные» и «проверенные, известные правильные данные». В вашем примере вы можете использовать uint8_t packet[10]; как тип данных для непроверенных данных и нормальную структуру (с заполнением и без __attribute__((packed));) для проверенных данных. Это делает чрезвычайно трудным для программиста случайное использование небезопасных данных, когда они думают, что используют безопасные / проверенные данные.
Конечно, вам также понадобится код для преобразования между этими типами данных, который должен выполнять как можно больше проверок работоспособности (и, возможно, также беспокоиться о таких вещах, как порядок байтов). Для вашего примера эти проверки могут быть:
Обратите внимание, что эта функция должна возвращать какой-то статус, чтобы указать, было ли преобразование успешным или нет, и в большинстве случаев этот статус также должен указывать, в чем была проблема, если преобразование не было успешным (чтобы вызывающий мог сообщить пользователя, или зарегистрируйте проблему, или решите проблему наиболее подходящим для нее способом). Например, возможно, «неизвестный идентификатор производителя» означает, что программу необходимо обновить для работы с новым производителем и что данные верны, а «неверный идентификатор производителя» означает, что данные определенно неверны.
Ха-ха, я увидел __attribute__((packed)) и инстинктивно отказался - ну, теперь все наоборот.
но, возможно, используйте для ввода uint8_t.
@AnttiHaapala: Исправлен uint8_t :-)
Для полностью переносимой версии я предлагаю вам читать следующим образом:
struct {
uint16_t e1;
uint8_t e2;
uint16_t e3;
} d;
uint8_t *cursor;
uint8_t rbuf[5];
read(sock, rbuf, sizeof(rbuf));
memcpy(&s.e1, &rbuf[0], sizeof(s.e1));
s.e2 = rbuf[2];
memcpy(&s.e3, &rbuf[3], sizeof(s.e3));
s.e1 = ntohs(s.e1);
s.e3 = ntohs(s.e3);
У вас может возникнуть соблазн сделать что-то вроде того, о чем говорили другие, например:
struct s {
uint16_t e1;
uint8_t e2;
uint16_t e3;
} __attribute__((packed));
struct s d;
read(sock, &d, sizeof(d));
s.e1 = ntohs(s.e1);
s.e3 = ntohs(s.e3);
Однако этот код не является полностью переносимым и может привести к проблемам, поскольку вы обращаетесь к элементам (s.e3) с невыровненной памятью, что само по себе является неопределенным поведением. В некоторых случаях этот способ приемлем и желателен (меньшее загрязнение кеша, поскольку большее количество структур может заполнять разные строки кеша и, возможно, более простой код), но в других случаях это может вызвать ошибку шины и сделать ваш код несовместимым с некоторыми архитектурами.
Помимо этого, вы должны следовать другим передовым методам, например, пытаться читать как можно больше структур между вызовами read(), делать более приятный код для перевода порядка байтов от сети к хосту ... но я думаю, что следует избегать нестандартного атрибута. будь первым.
Обратите внимание, что если вы НЕ выполняете невыровненный доступ, все это (даже атрибут __packed__) совершенно не нужно, и вы можете читать такие структуры, как:
struct {
uint16_t e1;
uint8_t e2;
uint8_t e2;
uint16_t e3;
} d;
read(rsock, &d, sizeof(d));
данные двоичные или текстовые?