JoseJimRin
JoseJimRin

Reputation: 402

Header with restrictions using bytes for an UDP Socket

I am doing a Header for an UDP socket which have a restrictions using bytes.

| Packet ID (1 byte) | Packet Size (2 bytes) | Subpacket ID (1 Byte) | etc

I did an struct for store this kind of attributes like:

typedef struct WHEATHER_STRUCT
{
   unsigned char packetID[1];
   unsigned char packetSize[2];
   unsigned char subPacketID[1];
   unsigned char subPacketOffset[2];
   ...
} wheather_struct;

I initialized this struct using new and I updated the values. The question is about if I want to use only 2 bytes in Packet Size attribute. What of these two forms that I wrote below is the correct one?

*weather_struct->packetSize = '50';

or

*weather_struct->packetSize = 50;

Upvotes: 0

Views: 270

Answers (3)

catnip
catnip

Reputation: 25388

If you can use C++11 and gcc (or clang) then I would do this:

typedef struct WHEATHER_STRUCT
{
   uint8_t packetID;
   uint16_t packetSize;
   uint8_t subPacketID;
   uint16_t subPacketOffset;
   // ...
}  __attribute__((packed)) wheather_struct;

If you can't use C++11 then you can use unsigned char and unsigned short instead.

If you're using Visual C then you can do:

#pragma pack (push, 1)
typedef struct ...
#pragma (pop)

Beware also byte ordering issues, depending on what architectures you need to support. You can use htons() and ntohs() to overcome this problem.

Live demo at Wandbox

Upvotes: 2

Richard Hodges
Richard Hodges

Reputation: 69892

Packing and unpacking data from IP packets is a problem as old as the internet itself (indeed, older).

Different machine architectures have different layouts for representing integers, which can cause problems when communicating between machines.

For this reason, the IP stack standardises on encoding integers in 'network byte order' (which basically means most significant byte first).

Standard functions exist to convert values in network byte order to native types and vice versa. I urge you to consider using these as your code will then be more portable.

Furthermore, it makes sense to abstract data representations from the program's point of view. c++ compilers can perform the conversions very efficiently.

Example:

#include <arpa/inet.h>
#include <cstring>
#include <cstdint>

typedef struct WEATHER_STRUCT
{
   std::int8_t packetID;
   std::uint16_t packetSize;
   std::uint8_t subPacketID;
   std::uint16_t subPacketOffset;
} weather_struct;


const std::int8_t* populate(weather_struct& target, const std::int8_t* source)
{
    auto get16 = [&source]
    {
        std::uint16_t buf16;
        std::memcpy(&buf16, source, 2);
        source += 2;
        return ntohs(buf16);
    };
    target.packetID = *source++;
    target.packetSize = get16();
    target.subPacketID = *source++;
    target.subPacketOffset = get16();
    return source;
}

uint8_t* serialise(uint8_t* target, weather_struct const& source)
{
    auto write16 = [&target](std::uint16_t val)
    {
        val = ntohs(val);
        std::memcpy(target, &val, 2);
        target += 2;
    };

    *target++ = source.packetID;
    write16(source.packetSize);
    *target++ = source.subPacketID;
    write16(source.subPacketOffset);
    return target;
}

https://linux.die.net/man/3/htons

here's an link to a c++17 version of the above:

https://godbolt.org/z/oRASjI

A further note on conversion costs:

Data arriving into or leaving your program is an event that happens once per payload. Suffering a conversion cost here incurs a negligible penalty.

Once the data has arrived in your program, or before it leaves, it may be manipulated many times by your code.

Some processors architectures suffer huge performance penalties during data access if data is not aligned on natural word boundaries. This is why attributes such as packed exist - the compiler is doing all it can to avoid misaligned data. Using a packed attribute is tantamount to deliberately telling the compiler to produce very suboptimal code.

For this reason, I would recommend not using packed structures (e.g. __attribute__((packed)) etc) for data that will be referred to by program logic.

Compared to RAM, networks are many orders of magnitude slower. A minuscule performance hit (literally nanoseconds) at the point of encoding or decoding a network packet is inconsequential compared to the cost of actually transmitting it.

Packing structures can cause horrible performance issues in program code and often leads to portability headaches.

Upvotes: 1

Alan Birtles
Alan Birtles

Reputation: 36399

Neither is correct, you need to treat the two bytes as a single 16-bit number. You probably also need to take into account the different endianness of the network stream to your processor architecture (depending on the protocol, most are big endian).

The correct code would therefore be:

*((uint16_t*)weather_struct->packetSize) = htons(50);

It would be simpler if packetSize were uint16_t to start with:

weather_struct->packetSize = htons(50);

Upvotes: 0

Related Questions