Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send a struct over a socket with correct padding and endianness in C

I have several structures defined to send over different Operating Systems (tcp networks). Defined structures are:

struct Struct1 { uint32_t num; char str[10]; char str2[10];}
struct Struct2 { uint16_t num; char str[10];}   

typedef Struct1 a;
typedef Struct2 b;

The data is stored in a text file. Data Format is as such:

  • 123
  • Pie
  • Crust

Struct1 a is stored as 3 separate parameters. However, struct2 is two separate parameters with both 2nd and 3rd line stored to the char str[] . The problem is when I write to a server over the multiple networks, the data is not received correctly. There are numerous spaces that separate the different parameters in the structures. How do I ensure proper sending and padding when I write to server? How do I store the data correctly (dynamic buffer or fixed buffer)?

Example of write: write(fd,&a, sizeof(typedef struct a)); Is this correct?

Problem Receive Side Output for struct2:

  • 123( , )
  • 0 (, Pie)
  • 0 (Crust,)

Correct Output

123(Pie, Crust)

like image 373
George Lee Avatar asked Apr 14 '11 07:04

George Lee


4 Answers

write(fd,&a, sizeof(a)); is not correct; at least not portably, since the C compiler may introduce padding between the elements to ensure correct alignment. sizeof(typedef struct a) doesn't even make sense.

How you should send the data depends on the specs of your protocol. In particular, protocols define widely varying ways of sending strings. It is generally safest to send the struct members separately; either by multiple calls to write or writev(2). For instance, to send

struct { uint32_t a; uint16_t b; } foo;

over the network, where foo.a and foo.b already have the correct endianness, you would do something like:

struct iovec v[2];
v[0].iov_base = &foo.a;
v[0].iov_len  = sizeof(uint32_t);
v[1].iov_base = &foo.b;
v[1].iov_len  = sizeof(uint16_t);
writev(fp, v, 2);
like image 184
Fred Foo Avatar answered Oct 20 '22 13:10

Fred Foo


Sending structures over the network is tricky. The following problems you might have

  1. Byte endiannes issues with integers.
  2. Padding introduced by your compiler.
  3. String parsing (i.e. detecting string boundaries).

If performance is not your goal, I'd suggest to create encoders and decoders for each struct to be send and received (ASN.1, XML or custom). If performance is really required you can still use structures and solve (1), by fixing an endianness (i.e. network byte order) and ensure your integers are stored as such in those structures, and (2) by fixing a compiler and using the pragmas or attributes to enforce a "packed" structure.

Gcc for example uses attribute((packed)) as such:

struct mystruct {
  uint32_t  a;
  uint16_t b;
  unsigned char text[24];
} __attribute__((__packed__));

(3) is not easy to solve. Using null terminated strings at a network protocol and depending on them being present would make your code vulnerable to several attacks. If strings need to be involved I'd use an proper encoding method such as the ones suggested above.

like image 24
Nikos Avatar answered Oct 20 '22 12:10

Nikos


The easy way would be to write two functions for each structure: one to convert from textual representation to the struct and one to convert a struct back to text. Then you just send the text over the network and on the receiving side convert it to your structures. That way endianness does not matter.

like image 35
sl0815 Avatar answered Oct 20 '22 14:10

sl0815


There are conversion functions to ensure portability of binary integers across a network. Use htons, htonl, ntohs and ntohl to convert 16 and 32 bit integers from host to network byte order and vice versa.

like image 28
Klas Lindbäck Avatar answered Oct 20 '22 13:10

Klas Lindbäck