Лучшая практика для анализа данных смешанного типа?

Мне интересно, есть ли какая-либо известная передовая практика / метод для анализа смешанного типа пакета данных.

Например, предположим, что данные состоят из 10 байт и состоят из:

Байт 0-1: идентификатор производителя (int)
Байт 2: тип (целое)
Байт 3-4: идентификатор устройства (ascii char)

Я мог бы просто определить размер и расположение каждого типа данных как #define и проанализировать его, используя эти определения. Но мне интересно, есть ли какая-нибудь структура, которая могла бы это лучше организовать.

данные двоичные или текстовые?

Swordfish 10.11.2018 05:42

Если вы заранее знаете определение пакета, почему бы просто не определить настраиваемую структуру со всеми необходимыми полями?

MikeFromCanmore 10.11.2018 05:44

Вы должны читать данные точно так же, как вы их написали ... Поскольку похоже, что это двоичный файл ...

Ruks 10.11.2018 05:44

Проходят ли данные через файлы для других платформ? Разместите несколько образцов входных данных и экспортированных пакетов выходных данных. Иначе это слишком широко / неясно.

chux - Reinstate Monica 10.11.2018 05:44

«Байт 0-4» составляет 5 байтов ... и данные должны быть 10 байт ...?

Ruks 10.11.2018 05:47

@Ruks, может быть, это очень, очень большие байты * duck-'n-run *

Swordfish 10.11.2018 05:48

"data is 10 bytes" -> Как определяется расширение 5 байт?

chux - Reinstate Monica 10.11.2018 05:59

@Jinsuk: «Я мог бы просто определить размер и расположение каждого типа данных как #define и проанализировать его с помощью этих определений» -> Мне искренне жаль, что вы это сделали, опубликовали этот код и затем спросили о лучших практиках. Это добавило бы информации и сделало бы хороший пост. Это слишком широко.

chux - Reinstate Monica 10.11.2018 06:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
130
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Нравится:

struct packet {
    uint16_t mfg;
    uint8_t type;
    uint16_t devid;
} __attribute__((packed));

Атрибут Packaging (или его эквивалент для вашей платформы) необходим, чтобы избежать неявного заполнения, которого нет в протоколе.

Когда у вас есть указанная выше структура, вы просто приводите (часть) массива char, который вы получили откуда угодно:

char buf[1000];
(struct packet*)(buf + N);
Обычные подозреваемые: Компилятор C жалобы может не иметь возможности упаковки. Если пакет пришел с другой машины, порядок байтов может отличаться.
chux - Reinstate Monica 10.11.2018 05:58

Да. вот так :) ... @ chux, предположительно, если вы знаете определение пакета данных, вы также знаете, какой порядок байтов он использует. Если вам нужно, вы можете поменять местами бит / байт перед преобразованием с помощью структуры.

MikeFromCanmore 10.11.2018 06:07

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

Antti Haapala 10.11.2018 09:20

@AnttiHaapala: Я не использовал массив символов. Я использовал buf + N, который является символом *. Который, насколько я понимаю, можно использовать (есть исключение для строгого псевдонима для char *). Что ты об этом думаешь?

John Zwinck 10.11.2018 11:54

Строгий псевдоним для char * - да, но это обратное направление. В нем говорится, что вы можете обращаться к байтам любого объекта как к символьному типу. Это не говорит о том, что вы можете получить доступ к элементам массива символов как к любому типу объекта.

Antti Haapala 10.11.2018 14:55
Ответ принят как подходящий

Лучше всего предполагать, что все данные извне программы (например, от пользователя, из файла, из сети, из другого процесса) потенциально неверны (и потенциально небезопасны / вредоносны).

Затем, исходя из предположения о «потенциальной некорректности», определите типы, чтобы различать «непроверенные, потенциально неверные данные» и «проверенные, известные правильные данные». В вашем примере вы можете использовать uint8_t packet[10]; как тип данных для непроверенных данных и нормальную структуру (с заполнением и без __attribute__((packed));) для проверенных данных. Это делает чрезвычайно трудным для программиста случайное использование небезопасных данных, когда они думают, что используют безопасные / проверенные данные.

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

  • являются любыми байтами, которые должны быть символами ASCII> = 0x80, и являются ли какие-либо из них недопустимыми (например, возможно, управляющие символы, такие как backspace, не разрешены).
  • действителен ли идентификатор производителя (например, может быть, есть перечисление, которому он должен соответствовать)
  • допустим ли тип (например, может быть, есть перечисление, которому он должен соответствовать)

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

Ха-ха, я увидел __attribute__((packed)) и инстинктивно отказался - ну, теперь все наоборот.

Antti Haapala 10.11.2018 09:17

но, возможно, используйте для ввода uint8_t.

Antti Haapala 10.11.2018 09:18

@AnttiHaapala: Исправлен uint8_t :-)

Brendan 10.11.2018 14:12

Для полностью переносимой версии я предлагаю вам читать следующим образом:

    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));

Другие вопросы по теме