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".
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