Data representation for truncated network packet headers

Suppose we're writing a tcpdump-like utility that needs to decode network packet headers, and, in particular, needs to be able to represent arbitrary network packet headers, including malformed ones.

A structured representation of all the details of an IPv4 packet header might look something like this (where UBits<n> is an unsigned integer type with exactly n value bits):

// visibility annotations omitted
struct Ipv4Header {
    version: UBits<4>,
    length: UBits<4>,
    dscp: UBits<6>,
    ecn: UBIts<2>,
    length: u16,
    identification: u16,
    reserved_bit: bool,
    dont_fragment: bool,
    more_fragments: bool,
    fragment_offset: UBits<13>,
    ttl: u8,
    protocol: u8,
    checksum: u16,
    source_addr: u32,
    dest_addr: u32,
    options: [Ipv4Option],
}

But this assumes the header is complete. What if we get a packet with an abnormally short length field, or one that's been truncated before the length specified in that field? We'd still like to decode as much as we can, and this is where I get stuck. I can think of only three ways to represent the result of partial decoding, all of which have problems:

  • enum PartialIpv4Header with a variant for each point at which the header could have been truncated. This requires an enormous amount of boilerplate code - quadratic in the number of fields - and I don't think it's possible to generate the boilerplate from a declarative macro, because there's no way to take a subslice of the stuff matched by a $( ... )+.
  • Independent structs for each point at which the header could've been truncated, with a trait for their common API. Also requires quadratic amounts of boilerplate.
  • Wrap every field in an Option. The problem with this one is, truncation can only chop fields off the end; if we have the identification field then we must also have all the fields that come before it on the wire. Calling code winds up having to cater to impossible scenarios.

Does anyone have a better idea? (note: please do not suggest proc macros, I am currently not using any and I want to keep it that way)

From user perspective this is the same as if each field was wrapped in an Option.