fit ip header into Bit Fields

Started by
16 comments, last by Zoomby 21 years, 1 month ago
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]
-- Joshua Lusion           | If you will tell me why the fen Sr. Software Engineer/  | appears impassable, I then hobbyist game coder     | will tell you why I think that I                         | can get across it if I try. (M. Moore)
Advertisement
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]
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
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]
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
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
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 ucharstruct 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]
-- Joshua Lusion           | If you will tell me why the fen Sr. Software Engineer/  | appears impassable, I then hobbyist game coder     | will tell you why I think that I                         | can get across it if I try. (M. Moore)
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]
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
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]
-- Joshua Lusion           | If you will tell me why the fen Sr. Software Engineer/  | appears impassable, I then hobbyist game coder     | will tell you why I think that I                         | can get across it if I try. (M. Moore)
Nah your first response was what I figured. It has been a *long* time since I''ve had a use for bit-fields.
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com

This topic is closed to new replies.

Advertisement