intrigued_66
intrigued_66

Reputation: 17248

Reading a 6 byte, big endian binary field representing an integer

I have binary, big-endian consisting of a message like:

struct Msg
{
    // 4 byte int
    // 6 byte int
    // 2 byte int
};

and I wish to read the 6 byte integer.

I haven't encountered a member before which wasn't 1, 2, 4 or 8 bytes before.

I'm not concerned with portability, you can assume this is on a Linux system using GCC compiler.

To read this should I do:

struct Msg
{
    uint32_t a;
    uint16_t b;   // Part of 6 byte field
    uint32_t c;   // Part of 6 byte field
    uint16_t d;
}

and then do I reinterpret_cast the entire message, swap the bytes (to convert to little endian) for b and c and then multiply the value of b by FFFF before adding to c?

Msg msg = *reinterpret_cast<Msg*>(&bytes[0]);
value = (__builtin_bswap16(msg.b) << 32) + __builtin_bswap32(msg.c);

Upvotes: 1

Views: 694

Answers (3)

Slava
Slava

Reputation: 44258

I think it is simpler and easier to write a function which will convert int of arbitrary size:

uint64_t readBigEndInt( const unsigned char *buff, size_t size )
{
    uint64_t r = 0;
    while( size-- )
        r = (r << 8) + *buff++;
    return r;
}

and just apply it to parts of raw buffer accordingly.

Also thanks for @MSalters if you care about speed, make this function template:

template<size_t size>
uint64_t readBigEndInt( const unsigned char *buff )
{
    uint64_t r = 0;
    for( size_t i = 0; i < size; ++i )
        r = (r << 8) + *buff++;
    return r;
}

so compiler can do better optimizations and you can provide specializations for 1,2,4 and 8 bytes if necessary. Also I would wrap it into thin class representing stream, that keeps current position and shift it by read operation. So your code at the end would be something like:

stream s(buffer,size);
uint32_t a = s.read<4>();
uint64_t b = s.read<6>();
uint16_t c = s.read<2>();

and so on.

Upvotes: 3

MSalters
MSalters

Reputation: 179819

By far the easiest non-portable solution would be to read the 6+2 byte members into a single std::uint64_t, extract the lower 16 bits and byte-swap those, and then byte-swap the remaining 6 bytes (which will neatly move them down into the lower 48 bits)

Remember to zero the lower 16 bits after extracting them.

Upvotes: 1

Useless
Useless

Reputation: 67733

The only portable way to handle this is as Kamil suggests in comments, to use a char array of the correct size, and manually extract fields from the correct offsets. You can certainly write wrapper machinery to make this nicer if you're doing this enough to make it worthwhile.

For your struct approach to work correctly, you need a non-portable #pragma pack or __attribute__((packed)) or other compiler-specific way to prevent normal padding and alignment of the members.

Upvotes: 0

Related Questions