Archived

This topic is now archived and is closed to further replies.

Zoomby

fit ip header into Bit Fields

Recommended Posts

hi I have a simple struct which I want to use for IP headers. But when I copy a buffer (with the ip header) to the struct''s memory, the both values a and b are flipped. a is not "4" and b is not "5" like it should be. What''s the problem? struct test { unsigned char a: 4; unsigned char b: 4; }; int main() { test t; unsigned char value=69; memcpy(&t,&value,1); cout<<(int)t.a<

Share this post


Link to post
Share on other sites
t.a should be equal to 69 and t.b will be undefined. The reason is that you are only copying 1 byte into your 2 byte structure (assuming padding is off here.)

Are you trying to put 6 & 9 into your struct?
If that is what you are trying to do then you need an unsigned short variable.


int main()
{
test t;
unsigned short value = 1545; // value 0000110 00001001
// value 6 9

// this assumes memory padding/alignment is "off"!
memcpy(&t, &value, sizeof(value));

cout << (int)t.a << cout << (int)t.b << endl;
}


Hope this helps.




[edited by - Digitalfiend on March 6, 2003 12:23:16 PM]

Share this post


Link to post
Share on other sites
Cool problem. I am not a bit field expert, but here is my theory.
Member a of struct test is declared to use 4 bits of an unsigned char. It uses the lowest 4 bits. Member b also uses 4 bits of the same unsigned char and gets the upper 4 bits.
69 = 0x45 = 01000101b
When this is memcpy''d into t, b gets 0100b and a gets 0101b.

Shawn

Share this post


Link to post
Share on other sites
@Digitalfiend
take a closer look at the structure. it''s not a 2 byte structure, it''s a one byte strucure with two 4 bit vars.

@ShawnO
your right. but that means there''s a kind of endianism within the byte. I thought big and little endian only appears if you got a number with more than one byte?

who does exactly know whats going on?

ciao
chris

Share this post


Link to post
Share on other sites
K&R says this about bit-fields:
quote:

Fields are assigned left to right on some machines and right to left on others.


In other words, the ordering of the data is implementation defined.

MSDN C Bit Field documentation says this:
quote:

Bit fields are allocated within an integer from least-significant to most-significant bit.


MSDN C++ Bit Field documentation says this:
quote:

The ordering of data declared as bit fields is from low to high bit.


FYI - The C99 standard does not require an implemenation to provide bit fields of type unsigned char.

Shawn

Share this post


Link to post
Share on other sites
First of all, it _is_ saving space, second he''s apparently trying to store an IP packet header in this struct, and he can''t just change the layout of IP packets. Now, why he''s trying to mess with IP headers at all is a mystery to me.

cu,
Prefect

Share this post


Link to post
Share on other sites
@Digitalfiend:

It doesn''t have to do anything with saving space. Imagine you received a buffer with an IP header (and maybe more). The IP header (and many more protocols) have a lot of flags and less than 8 bit-datatypes in it (take a look at RFC:791). So when I have a struct (with bit fields) that has the same layout as the ip header all I have to is a memcpy() from the receive buffer to the IP header-struct (and don''t iterate throught the buffer by hand and extract the parameter or so). Now I can access every parameter in the header easiely.

@Prefect:
I don''t want to change the layout of the IP packets. The struct I use in the first post is just an example of the main problem (the problem was how exactly bit fields are saved in memory)

You need to take a look at IP headers when you are:
-just interested in it (would you believe that? :-) )
-receiving ICMP echo replies.
-programing a packet sniffer.

bye
chris

Share this post


Link to post
Share on other sites
quote:
Original post by Zoomby
...
What's the problem?



This is a bit endian issue. Compiler makers typically set up compilers like this on little endian machines. This way, if struct test is overlaying a native machine byte, then field a represents bits 0 through 3, and this is also true if the struct test overlays a native machine short, int, or long.

As such, bit endianness tends to be little endian on little endian machines. When you structure your bit fields, just keep this in mind, and think of starting at bit 0 rather than "going left to right" so to speak.

[edited by - yy2bggggs on March 9, 2003 3:02:12 PM]

[edited by - yy2bggggs on March 9, 2003 3:04:03 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by Zoomby
@Digitalfiend:

It doesn't have to do anything with saving space. Imagine you received a buffer with an IP header (and maybe more). The IP header (and many more protocols) have a lot of flags and less than 8 bit-datatypes in it (take a look at RFC:791). So when I have a struct (with bit fields) that has the same layout as the ip header all I have to is a memcpy() from the receive buffer to the IP header-struct (and don't iterate throught the buffer by hand and extract the parameter or so). Now I can access every parameter in the header easiely.



I understand what you are saying. For some reason I just figured that you were using an IP header as an example of what you wanted to accomplish with your own gaming protocol.

Personally, I'd still read the bit fields into a word (take the "flags" and "fragment offset" bit fields, for example) and provide accessor methods (inlined if need be) to extract the appropriate data from the word (shifting as necessary.)

Then I'd provide serialize/deserialize methods to allow me to copy the header from a byte stream.

This is more portable to other compilers and machines. The problem is that bit-field storage is implementation defined. You can't assume whether one bit-field will be in the high or low section of a byte.

Sorry if I misunderstood what you were getting at...



[edited by - Digitalfiend on March 10, 2003 4:53:46 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by Prefect
First of all, it _is_ saving space,


Well, if you think about it, you aren't really saving space. Bit-fields are more of a convenience feature, in my opinion.

Think about it:


struct
{
unsigned char a: 4;
unsigned char b: 4;
}


takes the same space (in memory) as:


struct
{
unsigned char ab;

unsigned char A()
{
return ab & 0x0F;
}

unsigned char B()
{
return ab >> 4;
}
}


So are you really saving space? No. Is the first example easier to access? That is a subjective question.

I personally like the second method and if I want to do a memcpy of a buffer and overlay my data structure it will work and be more portable.


[edited by - Digitalfiend on March 10, 2003 4:54:19 PM]

Share this post


Link to post
Share on other sites
thanks for your opinion. Your method is nice too.

A propos more portable:

if you don''t know if a bit-field will be in the high or low section of a byte, you can''t know how to mask and shift to get the right bits!? That would mean, that such code is never portable? Or am I wrong?

bye
chris

Share this post


Link to post
Share on other sites
quote:
Original post by Zoomby
...
if you don't know if a bit-field will be in the high or low section of a byte, you can't know how to mask and shift to get the right bits!? That would mean, that such code is never portable? Or am I wrong?



You're not entirely wrong, but you're not exactly right (not to sound like a politician).

DigitalFiend's method isn't actually portable as-is , but can easily be made somewhat portable by appropriate use of hton/ntoh functions. This would be a lot harder to do if you use bit fields.

Bit fields are nice, however, and you should at least consider using them (I'm not necessarily saying to use them, but I challenge you to at least consider if they are the right solution). Especially on little endian machines such as the one you're likely working with, bit fields are easier to "see" than DigitalFiend's method. e.g.:


      
// WARNING: DO NOT USE this code! Each of these structures

// contains a bug. The point is to find out how easy it is

// to find the bug in each version.

struct TcpHeaderBits
{
//-- RFC 752 Figure 3 row 1

unsigned SrcPort : 16;
unsigned DstPort : 16;
//-- RFC 752 Figure 3 row 2

unsigned Sequence : 32;
//-- RFC 752 Figure 3 row 3

unsigned AckNum : 32;
//-- RFC 752 Figure 3 row 4

unsigned DataOffset : 3;
: 6;
unsigned URG : 1;
unsigned ACK : 1;
unsigned PSH : 1;
unsigned RST : 1;
unsigned SYN : 1;
unsigned FIN : 1;
unsigned Window : 16;
//-- RFC 752 Figure 3 row 5

unsigned Checksum : 16;
unsigned UrgentPtr : 16;
//-- RFC 752 Figure 3 row 6

unsigned Options : 24;
: 8;
};

// not shown--helper #define's for uint#'s and uchar

struct TcpHeaderMethods
{
uint32 Row1;
uint32 Row2;
uint32 Row3;
uint32 Row4;
uint32 Row5;
uint32 Row6;
inline uint16 SrcPort() {return (Row1>>16) & 0x0ffff;}
inline uint16 DstPort() {return (Row1) & 0x0ffff;}
inline uint32 Sequence() {return Row2;}
inline uint32 AckNum() {return Row3;}
inline uchar DataOffset(){return (Row4>>29) & 0x0f;}
inline uchar URG() {return (Row4>>21) & 0x01;}
inline uchar ACK() {return (Row4>>20) & 0x01;}
inline uchar PSH() {return (Row4>>19) & 0x01;}
inline uchar RST() {return (Row4>>18) & 0x01;}
inline uchar SYN() {return (Row4>>17) & 0x01;}
inline uchar FIN() {return (Row4>>16) & 0x01;}
inline uint16 CheckSum() {return (Row5>>16) & 0x0ffff;}
inline uint16 UrgentPtr(){return (Row5) & 0x0ffff;}
inline uint32 Options() {return (Row6>>8) & 0x0ffffff;}
};

(side note: RFC 752 happens to define bit ordering in such a way to make this layout easy to compare to Figure 3).

BTW, for the record, I will re-iterate that a working TcpHeaderMethod struct can be more easily incorporated into portable code.

On another topic, it's insane to memcpy to either of these structs in order to interpret raw data... just assign a pointer to one of these structures to the location you need to interpret:


              
TcpHeaderMethod *Header = (TcpHeaderMethod*)buffer;
if (Header->SYN()>0) blah();
// instead of:

// TcpHeaderMethod Header;

// memcpy(buffer, Header, sizeof(TcpHeaderMethod));

// if (Header.SYN()>0) blah();



[edited by - yy2bggggs on March 13, 2003 9:42:39 PM]

[edited by - yy2bggggs on March 14, 2003 9:40:16 AM]

Share this post


Link to post
Share on other sites
I really wonder if casting the address of the buffer to a TcpHeaderMethod pointer will result in the same problems as doing memcpy?


TcpHeaderMethod* p = (TcpHeaderMethod*)buffer;


Now say the first 4 bytes of the buffer are:


AA BB BB AA


Casting the buffer to a TcpHeaderMethod pointer, you'd expect that:



TcpHeaderMethod* p = (TcpHeaderMethod*)buffer;

ASSERT(p->SrcPort == 0xAABB);
ASSERT(p->DstPort == 0xBBAA);



But doesn't this lead us back to implementation details? It is up to the compiler to determine how much space a bit-field takes up as well as the location of the bits (high/low etc).

I'm not disputing your method as it is a perfectly valid technique that is used quite frequently when working with protocols defined with structures absent of bit-fields. The thing I'm curious about is if this technique will still hold true (and even portable) with bit-fields.



[edited by - Digitalfiend on March 14, 2003 2:59:45 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by Digitalfiend
I really wonder if casting the address of the buffer to a TcpHeaderMethod pointer will result in the same problems as doing memcpy?



Yes. Since the memory footprint of the source and the destination after a memcpy are identical, any compiler/platform specific endianness choices are inconsequential WRT the fact that it will interpret a struct at both locations in the same exact way.

UPDATE: After posting, it occurred to me you could have been asking something slightly different. There's no way a memcpy could change the endianness of something (be it byte or bit endianness)--if it did, it's a broken memcpy. There's also no possible way that a compiler could choose to implement a bit field one way at one memory location and a different way at another location--if it did, it's a broken compiler.

[edited by - yy2bggggs on March 14, 2003 5:45:29 PM]

[edited by - yy2bggggs on March 14, 2003 5:53:33 PM]

Share this post


Link to post
Share on other sites