Byte order question

Hi,

The structure that will follow is supposed to hold the following RTP header field

  0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                           timestamp                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
typedef struct rtp_header
{
#ifdef BIGENDIAN
    uint16_t version:2;
    uint16_t padbit:1;
    uint16_t extbit:1;
    uint16_t cc:4;
    uint16_t markbit:1;
    uint16_t paytype:7;
#else
    uint16_t cc:4;
    uint16_t extbit:1;
    uint16_t padbit:1;
    uint16_t version:2;
    uint16_t paytype:7;
    uint16_t markbit:1;
#endif
    uint16_t seq_number;

Shouldn't it be like the following

typedef struct rtp_header
{
#ifdef BIGENDIAN
    uint16_t version:2;
    uint16_t padbit:1;
    uint16_t extbit:1;
    uint16_t cc:4;
    uint16_t markbit:1;
    uint16_t paytype:7;
#else
    uint16_t markbit:1;
    uint16_t paytype:7;
    uint16_t version:2;
     uint16_t padbit:1;
     uint16_t extbit:1;
     uint16_t cc:4;
#endif
    uint16_t seq_number;

Or am I missing something?

Thanks in advance,
S.

[edit] this just gets stranger the more I look at it. I'm going to have to change my mind and say I don't know.

---------- Post updated at 10:12 AM ---------- Previous update was at 09:38 AM ----------

Just done some more research here, comparing bitfields on big and little endian machines I have access to, and learned something new. Bitfields reverse direction on big endian platforms. Considering the following code:

#include <stdio.h>
#include <string.h>

typedef union
{
        struct
        {
                unsigned int a:3;
                unsigned int b:3;
                unsigned int c:3;
                unsigned int d:2;
                unsigned int e:7;
                unsigned int f:8;
                unsigned int g:6;
        };
        /* Occupies the same memory as a through g, since this is a union */
        unsigned int z;
} bitmongler;

void print32(unsigned int q)
{
        int n;
        for(n=0; n<32; n++)
        {
                if(q & (1<<n))  printf("1");
                else            printf("0");
        }
        printf("\n");
}

#define INVERT(X)       (X)--

int main(void)
{
        bitmongler w;

        int n;
        for(n=0; n<7; n++)
        {
                memset(&w, 0, sizeof(w));

                switch(n)
                {
                case 0: INVERT(w.a);    break;
                case 1: INVERT(w.b);    break;
                case 2: INVERT(w.c);    break;
                case 3: INVERT(w.d);    break;
                case 4: INVERT(w.e);    break;
                case 5: INVERT(w.f);    break;
                case 6: INVERT(w.g);    break;
                }

                print32(w.z);
        }
        return(0);
}

On a little-endian platform it prints:

11100000000000000000000000000000
00011100000000000000000000000000
00000011100000000000000000000000
00000000011000000000000000000000
00000000000111111100000000000000
00000000000000000011111111000000
00000000000000000000000000111111

On a big-endian platform it prints:

00000000000000000000000000000111
00000000000000000000000000111000
00000000000000000000000111000000
00000000000000000000011000000000
00000000000000111111100000000000
00000011111111000000000000000000
11111100000000000000000000000000

So yes, it does need to reverse the direction of bits, not just bytes, because the compiler chooses blocks of bits in the reverse direction.

And yet, if we make INVERT subtract two instead of one, we get:

BE 00000000000000000000000000000011
vs
LE 01100000000000000000000000000000

so the blocks of bits are treated in the correct, normal direction, even if the direction they are grouped in is reversed. Weird! But I suspect there is a rational reason -- that being, when a grouping of bits crosses a byte or word boundary, to make sure the two halves split by the boundary are combined in the right order.

So the code as given was correct, strange as it looks.

I'm not so sure bit fields are portable.

You'd probably be better off using a uint32_t, bit-masking out the fields, and making sure you always use hton() to write the data and ntoh() to read it.

They may not work on some embedded platforms, but beyond that, they're fairly portable in the "compiles" sense, though where the bits actually end up depends a lot on your architecture. I'd tend to agree they're more trouble than just using binary logic operations.