Наиболее семантически правильная и безопасная для типов конструкция из сериализованного байтового массива? (C ++ 11)

Рассмотрим следующий класс c ++ 11, который представляет структуру заголовка IPv4, которая должна создаваться из байтового массива независимо от порядка байтов.

#include <arpa/inet.h>
#include <netinet/in.h>

namespace Net {
using addr_t = ::in_addr_t;
#pragma pack(push, 1)
struct ip_header_t {
uint8_t  ver_ihl;
uint8_t  tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_fo;
uint8_t  ttl;
uint8_t  protocol;
uint16_t checksum;
addr_t   src_addr;
addr_t   dst_addr;

ip_header_t( const uint8_t* bytes, const bool ntoh = false ) {
auto o = (ip_header_t&)*bytes;
ver_ihl      = o.ver_ihl;
tos          = o.tos;
ttl          = o.ttl;
protocol     = o.protocol;
total_length = ntoh? ntohs(o.total_length) : o.total_length;
id           = ntoh? ntohs(o.id) : o.id;
flags_fo     = ntoh? ntohs(o.flags_fo) : o.flags_fo;
checksum     = ntoh? ntohs(o.checksum) : o.checksum;
src_addr     = ntoh? ntohl(o.src_addr) : o.src_addr;
dst_addr     = ntoh? ntohl(o.dst_addr) : o.dst_addr;
};
};
#pragma pack(pop)
}

Я обеспокоен тем, что принятие байтового массива может быть не самым безопасным или наиболее семантически правильный способ сделать это. Приведение массива в качестве самой структуры кажется очень методом C-ish, в котором отсутствует безопасность типов (не говоря уже о проверке границ). Было бы лучше потребовать от вызывающего абонента беспокоиться об этом и потребовать константную ссылку на экземпляр?

0

Решение

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

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

0

Другие решения

Тип для представления необработанных двоичных данных с предполагаемой компоновкой некоторого вида:

template<typename T, size_t order>
struct serial_tag {};

Представляем некоторые имена, представляющие ожидаемый тип и расположение данных на диске:

typedef serial_tag<uint8_t , 0> ver_ihl_ser;
typedef serial_tag<uint8_t , 1> tos_ser;
typedef serial_tag<uint16_t, 2> total_length_ser;
...
typedef serial_tag<addr_t  , 9> dst_addr_ser;

Пакет serial_tags, которым затем можно манипулировать с помощью другого кода:

template<typename... tags>
struct serial_pack {};

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

Напишите код, который принимает итератор десериализации и serial_tag и продвигает этот итератор десериализации при генерации данных из serial_tag. Это должно обращаться с порядком байтов

Цель состоит в том, чтобы описать макет необработанных данных таким образом, чтобы его можно было метапрограммировать, а затем загрузить данные в структуру C ++ с использованием этой информации макета.

Это операция потокового чтения, где итератор десериализации (или диапазон) знает, имеет ли он ограничение на его размер, и знает, правильно ли вы читаете элементы по порядку (по крайней мере, в отладке).

Я не знаю, стоит ли это того, но это решает ваши проблемы.

Недостатком этого подхода является то, что он нарушает DRY, поскольку теоретически макет в памяти можно использовать для описания постсериализации необработанного байта. Вместо этого мы должны поддерживать совершенно другой набор данных, чтобы представить это. Как плюс, это означает, что наш макет в C ++ не должен точно копировать двоичный макет.

0

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

Вот так:

/* copy constructor: */
ip_header_t( const ip_header_t& src, const bool ntoh = false )
: ver_ihl(src.ver_ihl),
tos(src.tos),
ttl(src.ttl),
protocol(src.protocol) {
total_length = ntoh? ntohs(src.total_length) : src.total_length;
id           = ntoh? ntohs(src.id)           : src.id;
flags_fo     = ntoh? ntohs(src.flags_fo)     : src.flags_fo;
checksum     = ntoh? ntohs(src.checksum)     : src.checksum;
src_addr     = ntoh? ntohl(src.src_addr)     : src.src_addr;
dst_addr     = ntoh? ntohl(src.dst_addr)     : src.dst_addr;
};

/* client code using byte array in network-order */
auto ip_header = Net::ip_header_t((Net::ip_header_t&)*(byte_array), true);

Я не уверен на 100%, что мне больше нравится это решение. Возможно, еще лучше создать функцию, не являющуюся членом, которая делает это, учитывая, что перестановка порядка байтов не имеет прямого отношения к конструкции объекта. Также, возможно, это не законная обязанность класса заботиться о выравнивании и упорядочении полей.

0
По вопросам рекламы [email protected]