0
votes

I have a packet coming in from the wire and I have a struct that represents the packet:

typedef struct {
    uint8_t packetType;
    uint8_t remainingLength;
    uint16_t protocolNameLength;
    char protocolName[20];
    uint8_t protocolLevel;
    uint8_t connectFlags;
    uint16_t keepAlive;
} Connect;

I have the following C code that parses the packet:

void decodePacket(char *packet) {
 Connect *connect = NULL;
 connect = (Connect *) packet;
 // Here I access the different fields of the packet using the connect struct
 printf("Protocol Name is %s\n", connect->protocolName);
}

In the above protocol the strings are not null ternimated when they come in from the wire and hence I get some wierd characters when I print the protocol name. Is there any way to solve this without changing the protocol?

Also if the protocol name is > 20, the char buffer will overflow. Is there a way to solve this problem? Do I need to drop this approach of parsing packets and just use an index and parse each byte of the packet manually?

Thanks

3
If there is a dynamic length field, you have to parse it by hand.Sami Kuhmonen
protobuf-c might be of some use to you.ccKep
(1) Make sure the name is null terminated (so the maximum valid protocol name length is 19). (2) Make sure the protocol name is not more than 19 bytes long. Truncate or report an error if it is too big. Or, redesign the structure and serialization to use TLV (type, length, value) encoding and don't place arbitrary limits on the size of the protocol.Jonathan Leffler
@JonathanLeffler I can make sure that the protocol name is null terminated if I manually parse the packet. And if I do that, then I can also ensure that the protocol name length does not exceed 19 bytes. So are you suggesting that going manually is the only way to parse a packet containing dynamic fields like Sami Kuhmonen said?ssd20072
The code in decodePacket also violates the strict aliasing rule; if you want to persist with this then you should disable strict aliasing optimizations (different compilers have different switches for this)M.M

3 Answers

2
votes

The cast of the packet to the struct is incredibly dangerous, as you have little or no control of how the struct will actually be aligned in memory.

You need to read The Lost Art of C Structure Packing: http://www.catb.org/esr/structure-packing/

0
votes

There are a few ways; which is best depends on many factors.

1st way. If you read your packet a byte at a time, using a pointer or an index to write in the receiving buffer, then you know how many chars will come in, for protocolName, after having read protocolNameLength. Then you can 1) discard trailing chars in names too long for your struct; 2) add a NULL at the end of protocolName, so you can easily manage the name; 3) move the writing pointer in the correct location after having read protocolName. You can also reserve as many chars you want for protocolName, by declaring it as "char protocolName[many]": it will not have a big cost.

2nd way. You can receive a packet in a burst, in a buffer, and then memcpy() the received buffer into your struct, in two different operations. The first memcpy copies the first 4 members; the second copies the rest of the packet in the correct destination. Some pointer arithmetic has to be done for the second memcpy(). The first memcpy() would protect from names too long. Again, you can add a NULL at the end of the data copied with the first memcpy, so you have a real C string for protocolName.

3rd way. You can use, as receiving buffer, the struct itself, and then memmove() from the "real (received)" position of protocolLevel to the "correct (designed)" address. This memmove would always move data backward, assuming that you reserved enough space for protocolName[]. Again, you can put a NULL in the correct place if protocolName (or in the last element, 19 (20-1) in your example).

Given that protocolNameLength is declared with 16 bits, I can suppose that names could be very long, and it is strange. But if really this name can be so lengthy, then moving memory around would waste CPU.

Playing around with memcpy() / memmove() would have sense if you have many fields to manage, and you have to do that many times. And it is viable if you don't have big blocks to move, and you know how the compiler packs/pads the struct (this last requirement is needed anyway, if you want to superimpose a C struct on a wire protocol).

Personally I would prefer the first way, if possible; otherwise, if fields are not too many, parse the packet "by hand".

0
votes

In the above protocol the strings are not null terminated when they come in ... I get some weird characters when I print the protocol name. Is there any way to solve this without changing the protocol?

Limit the printing width with a precision for %s

If a precision is specified, no more than that many bytes are written C11dr §7.21.6.1 8

// printf("Protocol Name is %s\n", connect->protocolName);
printf("Protocol Name is %.*s\n", 
    (int)(sizeof connect->protocolName), 
    connect->protocolName);

"the strings are not null terminated" is a contradiction. In C, a string, as used in the standard library, is always null character terminated, else it is not a string, but simply a character array. Fortunately %.*s handles character arrays and strings, stopping at the given precision or the null character.


The following code is a problem @M.M with char * including alignment of Connect *. This can be solved with the higher un-posted code or as below. Using char* to decode packets is not a roust solution.

void decodePacket(char *packet) {
   Connect *connect = NULL;
   connect = (Connect *) packet;  // bad

// alternative
void decodePacket(char *packet) {
   Connect connect;
   memcpy(&connect, packet, sizeof connect);
   printf("Protocol Name is %.*s\n", 
       (int)(sizeof connect.protocolName), 
       connect.protocolName);

Also if the protocol name is > 20, the char buffer will overflow.

This is unclear. If the overflow occurs in the sender, then the receiving code can not fix that. OP's printf() code can be fixed as described above. OP has no other code that overflows any buffer.