C++: Populate a struct with data from a buffer

2019-09-20 17:50发布

问题:

I was wondering if I could have some recommendations on how to take data from a buffer and load them into a struct. For example, I have dealing with a DNS response buffer. I need to populate a DNS answer struct so that I can interpret the data. So far, I have the following:

int DugHelp::getPacket() {
    memset(buf, 0, 2000); // clearing the buffer to make sure no "garbage" is there
    if (( n = read(sock, buf, 2000)) < 0 {
        exit(-1);
    }
    // trying to populate the DNS_Answers struct
    dnsAnswer = (struct DNS_Answer *) & buf;
    . . .
} 

This is the struct that I have defined:

struct DNS_Answer{
    unsigned char name [255];
    struct {
        unsigned short type;
        unsigned short _class;
        unsigned int ttl;
        unsigned in len;
    } types;
    unsigned char data [2000];
};

回答1:

It depends on the data format of buf. If the format is same with DNS_Answer. You can use memcpy. If their formats are same, you should align the bytes first.

#pragma pack (1)
struct DNS_Answer{
    unsigned char name [255];
    struct {
        unsigned short type;
        unsigned short _class;
        unsigned int ttl;
        unsigned in len;
    } types;
    unsigned char data [2000];
};
#pragma pop(1)

Then,

memcpy(dnsAnswer, buf, sizeof(DNS_Answer));

If their data formats aren't same, you have to parse them by yourself, or you can use DFDL (A data format descripation language.)



回答2:

I do something a bit like this (rather untested) code:

Library Code:

namespace net {

using byte = unsigned char;

enum class endian
{
#ifdef _WIN32
    little = 0,
    big    = 1,
    native = little
#else
    little  = __ORDER_LITTLE_ENDIAN__,
    big     = __ORDER_BIG_ENDIAN__,
    native  = __BYTE_ORDER__,
#endif
};

constexpr bool is_little_endian()
{
    return endian::native == endian::little;
}

template<typename POD>
byte* write_to_buffer(POD const& pod, byte* pos)
{
    if(is_little_endian())
        std::reverse_copy((byte*)&pod, (byte*)& pod + sizeof(pod), pos);
    else
        std::copy((byte*)&pod, (byte*)& pod + sizeof(pod), pos);

    return pos + sizeof(pod);
}

template<typename POD>
byte const* read_from_buffer(byte const* pos, POD& pod)
{
    if(is_little_endian())
        std::copy(pos, pos + sizeof(pod), (byte*)&pod);
    else
        std::reverse_copy(pos, pos + sizeof(pod), (byte*)&pod);

    return pos + sizeof(pod);
}

} // namespace net

Application Code:

struct DNS_Answer{
    unsigned char name [255];
    struct {
        unsigned short type;
        unsigned short _class;
        unsigned int ttl;
        unsigned int len;
    } types;
    unsigned char data [2000];
};

net::byte* write_to_buffer(DNS_Answer const& ans, net::byte* buf)
{
    auto pos = buf;
    pos = net::write_to_buffer(ans.name, pos);
    pos = net::write_to_buffer(ans.types.type, pos);
    pos = net::write_to_buffer(ans.types._class, pos);
    pos = net::write_to_buffer(ans.types.ttl, pos);
    pos = net::write_to_buffer(ans.types.len, pos);
    pos = net::write_to_buffer(ans.data, pos);
    return pos;
}

net::byte const* read_from_buffer(net::byte const* buf, DNS_Answer& ans)
{
    auto pos = buf;
    pos = net::read_from_buffer(pos, ans.name);
    pos = net::read_from_buffer(pos, ans.types.type);
    pos = net::read_from_buffer(pos, ans.types._class);
    pos = net::read_from_buffer(pos, ans.types.ttl);
    pos = net::read_from_buffer(pos, ans.types.len);
    pos = net::read_from_buffer(pos, ans.data);

    return pos;
}

This should be pretty portable, deals with different byte orders and avoids potential alignment problems. You can also transfer non-pod types by breaking them down into several POD pieces and sending those separately. For example std::string can be sent as a std::size_t for the length and the rest as a char array.