Search code examples
cmacrosflexible-array-member

Should one be using macros instead of flexible array members?


Let's assume the following:

I'd like to create a structure for UDP packets. Each frame usually consists of an Ethernet Header, an IP header, a UDP header and an optional payload followed by, finally, the FCS (Frame Checksum Sequence).
The payload length is unknown/flexible. Meaning that when creating the struct, the payload would have to be the last member of it (flexible array member). Consequently, there's no place for the FCS.

So I thought about what possibilities would remain.

I came up with the following piece of code:

#define UDP_PKT(name, payload_length) struct __attribute((__packed__))      \
    {                                                                       \
        struct ether_header eth;                                            \
        struct ip iph;                                                      \
        struct udphdr udph;                                                 \
        unsigned char payload[payload_length];                              \
        u_int32_t fcs;                                                      \
    } name;

As this is not allowed:

struct __attribute__((__packed__)) udp_packet
{
    struct ether_header eth;
    struct ip iph;
    struct udphdr udph;
    unsigned char payload[]; // fam, must always be the last member
    u_int32_t fcs;
};

My question: Is that the only possibility I have to include the FCS in the structure without having a fixed array (payload) size?

If so, is that a good solution? Is that considered good practice?


Solution

  • The size of a struct with flexible array member is determined at runtime, so your first approach would not work either. The solution is to place FCS at the end of the buffer when you are ready to serialize your struct for the wire:

    struct __attribute__((__packed__)) udp_packet {
        struct ether_header eth;
        struct ip iph;
        struct udphdr udph;
        u_int32_t fcs;
        unsigned char payload[]; // Move fcs up
    };
    
    void serialize_udp_packet(const udp_packet* p) {
        // Compute buffer size, and allocate the buffer
        // Place the header into the buffer
        // Copy the payload into the buffer
        // Place FCS into the buffer at the end
    }
    

    You could even exclude fcs from the udp_packet altogether, and compute it only when you serialize the struct. One advantage of this approach is that you could freely mutate the payload without having to sync up the FCS to the changed payload all the time.