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];
};
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.)
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.