1

In our company it is very common to send a C/C++ structures over a network. Any struct has one or many uint32_t fields:

typedef struct {
    uint32_t data1;
    uint32_t data2;
} Data;

and fields (data1, data2, ...) encapsulates the real data we need to send. Our data is mostly of a single or few bits long, so to save space/bandwidth it is aligned inside those fields in a specific bit position.

To access our real data we write (outside of a struct!) a 'getters' and 'setters' macros with bitshifting and bit masking:

#define READY_MASK 0x01      // indicates that READY is a single bit value
#define READY_OFFSET 3       // indicates position of READY inside 32-bit field
#define IS_READY(x) { ... }  // returns READY value from x field
#define SET_READY(x,r) { ... }  // sets READY value to x field

Now I'd like to modify, simplify and make this process more secure by adding getters and setters directly into the structure, for example:

typedef struct {
    uint32_t data1;

    #define READY_MASK 0x01
    #define READY_OFFSET 3
    inline void set_ready(uint32_t r) { /*...*/ }
    inline uint32_t is_ready() { /*...*/ }
    // lots of other getters and setters
} Data1;

As far as my experiments are correct, I've noticed that this kind of modification doesn't affect in a size of structure sizeof(Data)==sizeof(Data1), I can send this structure over the network and receive and decode on the other end.

My question is: is there anything incorrect in this modification? Is there anything risky or anything that I should be aware of?

SP5RFD
  • 841
  • 3
  • 17
  • 31
  • 2
    That `Data1` structure is not a C structure, C structures can't have member functions. Are you sure you're not programming in C++? – Some programmer dude Nov 04 '15 at 12:12
  • Assuming it's C++ (because it just plain won't compile in C...), just how will adding functions to the structure make things more secure? – Andrew Henle Nov 04 '15 at 12:16
  • Of course @JoachimPileborg, Data1 is C++ – SP5RFD Nov 04 '15 at 12:16
  • @crooveck Then why have you tagged your question as being C? – unwind Nov 04 '15 at 12:18
  • it is more secure because functions allow to check arguments types in opposite to macros – SP5RFD Nov 04 '15 at 12:21
  • @unwind it is now C++ as well ;) – SP5RFD Nov 04 '15 at 12:22
  • Besides what @JoachimPileborg already stated (correctly), there are always caveats when trying to send complex data (structures) over the network as the data is transferred as a stream of bytes but can be interpreted differently by the communicating sides. For instance: Big / Little Endian, byte and bit alignment not the same for both processors. If your company deals with a lot of communication that is composed of structures, you should probably look for a commercial solution. My company has one such solution: www.adisw.com – o_weisman Nov 04 '15 at 12:23
  • By the way, if you only use certain bits for certain values, why not use [bit-fields](http://en.cppreference.com/w/c/language/bit_field)? Then you don't really need the macros or the getters/setters. – Some programmer dude Nov 04 '15 at 12:23
  • 3
    What if someone decides to declare one of the functions virtual? In my experience relying on structure layout breaks sooner or later. It is much better to use structures for in memory use only, and create proper serialization functions for converting it to the transmittable byte data. – user694733 Nov 04 '15 at 12:35
  • @JoachimPileborg bit-fields would be the best for our purpose, but the problem is that they are unsecure if transfered over network (https://www.securecoding.cert.org/confluence/display/c/EXP11-C.+Do+not+make+assumptions+regarding+the+layout+of+structures+with+bit-fields) – SP5RFD Nov 04 '15 at 12:36
  • 1
    I wouldn't call them "insecure" but yes there might be compiler specific incompatibilities. Just like for *all* structures. All structures may have padding in places that may differ between compilers or platforms. that has nothing to do with the network handling or "security", it's inherent in the compilers and the language specification. You would have the same problems saving structures, with or without bit-fields, to binary files and then reading them in another program. If you want to platform independent, you need to [serialize](https://en.wikipedia.org/wiki/Serialization) the structures. – Some programmer dude Nov 04 '15 at 12:42
  • @crooveck: Check if you are satisfied with the answers and approve one of them, and possibly upvote both ;) – Erik Alapää Nov 05 '15 at 14:32

2 Answers2

2

The modification you did doesn't make any harm but it also doesn't make any good as struct members are public anyway. The reason why Data and Data1 are same size if because what you added are inline functions which don't occupy space in the object but replace any function call by the actual function code.

Now, if you are sending structs and binary data through the network you must consider the following two rules:

  • By convention integer values are sent using network endianness which is Big Endian as opposed to x86's Little Endian. This is not a problem if you are sure that all machines sending and receiving data are x86. However, you will have problems if any machine has different endianness, like Big Endian or No Endian (or Middle Endian). For example, you can have an ARM processor, a SPARC, etc. In C you have the following macros:

    ntohs, htons, ntohl, htonl

  • You also have to consider different memory alignments. Memory alignment of structs depend on architecture, compiler, and compilation mode. Compilers may add padding (they cannot reorder members in C but read this: Can a C++ compiler re-order elements in a struct). Additionally types like long have different size in 32-bit and 64-bit architectures. Even though it is pretty sure your won't have problems with only two unsigned int members, you should never make a memory copy from the struct to the data buffer that will be sent in the message nor memory copy from raw data in the message to a struct. Basically you shouldn't do something like:

    char buffer[BUF_SIZE];

    Data1 myData;

    memcpy(buffer, myData, sizeof(Data));

https://en.wikipedia.org/wiki/Data_structure_alignment

You should move members to buffer one by one. The same when you populate a struct from raw data received in a message.

I would also suggest not using bit fields, read Why bit endianness is an issue in bitfields?. But to be very clear, in your snippet you are not using bitfields. It is not the same using bitfields as using bits in an integer to represent some specific status. In this case, you are doing good and should only consider integer representation in architectures with different endianness (if this is a possible case).

Community
  • 1
  • 1
rodolk
  • 5,606
  • 3
  • 28
  • 34
1

In principle, if you do not add virtual functions, there will be no vptr and struct size should be same as without the member functions. But it is not a POD struct anymore, so you may easily wander into the twilight zone of UB, Undefined Behavior. Since C++ does not (yet) have a standardized ABI, it is often good to resort to C-style POD structs for e.g. serialization over a network. Same goes for making lean, portable APIs for C++ modules/subsystems, stick to C ABI.

Also, I would advise you to stay away from bitfields if you consider those (as some commenter suggested), bitmasking manually is usually easier to analyze and make portable. Bitfields can e.g. give you endianness problems, this is at least partly because the first member of a struct 'always' ends up at the lower memory address from struct start, regardless of endianness. How bitfields work may also depend on the transfer medium, e.g. a BSD network stream socket is a byte stream, but many HW interfaces copy whole 32-bit word-sized chunks and not bytes.

Erik Alapää
  • 2,585
  • 1
  • 14
  • 25