deku icon indicating copy to clipboard operation
deku copied to clipboard

The whole design and the documentation around endianess and how they interact with `ctx` is confusing as heck

Open hechang27-sprt opened this issue 2 months ago • 5 comments

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big")]
pub struct IpHeader {
    #[deku(bits = 4)]
    version: u8,
    #[deku(ctx = "*version")]
    header: IpSubHeader,
}

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big", ctx = "version: u8", id = "version")]
pub enum IpSubHeader {
    #[deku(id = 4)]
    Ipv4(Ipv4Header),
    #[deku(id = 6)]
    Ipv6(Ipv6Header),
}

// type definition for Ipv4Header and Ipv6Header

Every thing is specified with endian = "big", yet I'm still getting these errors:

mismatched types
expected type `u8`
  found tuple `(Endian, u8)`
mismatched types
expected `()`, found `Endian`

hechang27-sprt avatar Nov 10 '25 07:11 hechang27-sprt

After reading through the documentation, and reading through several issues with the same error, I still didn't know that:

  1. you HAVE TO add a ctx = "_: Endian" to receive the endian setting from the parent struct
  2. At the time of writing this comment, I still don't know if child structs AUTOMATICALLY inherit endianess from parent if not specified in child struct, aka without the endian = "endian".

hechang27-sprt avatar Nov 10 '25 08:11 hechang27-sprt

  1. Please FIX the doc. Actually SPELL IT OUT that you have to add a ctx = "_: Endian" in child struct when you specify endian = XXX in parent struct. And SPELL IT OUT that whether child struct automatically inherit endianess from parent struct.
  2. Please consider using a struct to receive ctx, so that the user can choose to skip parameters they don't care about.
#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big", ctx = "{ version: u8 }", id = "version")]
pub enum IpHeader {
    #[deku(id = 4)]
    Ipv4(Ipv4Header),
    #[deku(id = 6)]
    Ipv6(Ipv6Header),
}

Or just

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
// #[deku(endian = "big", ctx = "_: Endian")]   // I don't care about specifying endianness on this level and I don't want to set ctx manually, just inherit from parent
pub struct Ipv4Header {
    #[deku(bits = 4)]
    pub ihl: u8, // Internet Header Length
    #[deku(bits = 6)]
    pub dscp: u8, // Differentiated Services Code Point
    #[deku(bits = 2)]
    pub ecn: u8, // Explicit Congestion Notification
    pub length: u16,         // Total Length
    pub identification: u16, // Identification
    #[deku(bits = 3)]
    pub flags: u8, // Flags
    #[deku(bits = 13)]
    pub offset: u16, // Fragment Offset
    pub ttl: u8,             // Time To Live
    pub protocol: u8,        // Protocol
    pub checksum: u16,       // Header checksum
    pub src: Ipv4Addr,       // Source IP Address
    pub dst: Ipv4Addr,       /* Destination IP Address */
}

hechang27-sprt avatar Nov 10 '25 08:11 hechang27-sprt

Here is the actual working code if you want to explicitly specify endianness, for those confused:

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big")]
pub struct IpPacket {
    #[deku(bits = 4)]
    version: u8,
    #[deku(ctx = "*version")]
    header: IpHeader,
}

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big", ctx = "_: Endian, version: u8", id = "version")]
pub enum IpHeader {
    #[deku(id = 4)]
    Ipv4(Ipv4Header),
    #[deku(id = 6)]
    Ipv6(Ipv6Header),
}

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big", ctx = "_: Endian")]
pub struct Ipv4Header {
    #[deku(bits = 4)]
    pub ihl: u8, // Internet Header Length
    #[deku(bits = 6)]
    pub dscp: u8, // Differentiated Services Code Point
    #[deku(bits = 2)]
    pub ecn: u8, // Explicit Congestion Notification
    pub length: u16,         // Total Length
    pub identification: u16, // Identification
    #[deku(bits = 3)]
    pub flags: u8, // Flags
    #[deku(bits = 13)]
    pub offset: u16, // Fragment Offset
    pub ttl: u8,             // Time To Live
    pub protocol: u8,        // Protocol
    pub checksum: u16,       // Header checksum
    pub src: Ipv4Addr,       // Source IP Address
    pub dst: Ipv4Addr,       /* Destination IP Address */
}

#[derive(Debug, PartialEq, Eq, DekuRead, DekuWrite)]
#[deku(endian = "big", ctx = "_: Endian")]
pub struct Ipv6Header {
    #[deku(bits = 6)]
    pub dscp: u8, // Differentiated Services Code Point
    #[deku(bits = 2)]
    pub ecn: u8, // Explicit Congestion Notification
    #[deku(bits = 20)]
    pub fl: u32, // flow label
    pub length: u16,
    pub protocol: u8,
    pub hl: u8, // hop limit
    pub src: Ipv6Addr,
    pub dest: Ipv6Addr,
}

hechang27-sprt avatar Nov 10 '25 08:11 hechang27-sprt

@hechang27-sprt thanks for the feedback. Agreed, the docs could use some love, the ctx system is particularly confusing.

Contributions are appreciated.

a good starting point would be expanding on the context docs in lib.rs or extending the endian-ness/ctx here: https://github.com/sharksforarms/deku/blob/36fc010a8714cf0f7e7954e9b08b19b95e5e94f4/src/attributes.rs#L118-L168

sharksforarms avatar Nov 10 '25 13:11 sharksforarms

Just to add - it's not just endianness, probably any customization. I spent a lot of time debugging an issue that stemmed from me only having ctx = "order: Order" on a child type - I thought that's enough to reuse parent's bit order, but the parsing kept failing until I added an explicit bit_order = "lsb" in the child too.

Feels pretty weird to have to duplicate it in two forms, but perhaps docs would clear it up.

RReverser avatar Nov 10 '25 19:11 RReverser