sharksforarms / deku

Declarative binary reading and writing: bit-level, symmetric, serialization/deserialization
Apache License 2.0
1.11k stars 54 forks source link

Reference whole input from sub struct #265

Closed inflation closed 2 years ago

inflation commented 2 years ago

Hi, I'm experimenting some DNS packet parsing. It's great so far, but one of the name resolving field uses an offset of the whole packet, which is not available in a sub structure. I tried to pass it through context, but ctx does not accept deku::input or other internal variables. So, how could I achieve that?

#[derive(Clone, Debug, Default, DekuRead, DekuWrite)]
#[deku(endian = "big")]
pub struct DnsPacket {
    pub header: DnsHeader,
    #[deku(count = "header.questions")]
    pub questions: Vec<DnsQuestion>,
    #[deku(count = "header.answers")]
    pub answers: Vec<DnsRecord>,
    #[deku(count = "header.authoritative_entries")]
    pub authorities: Vec<DnsRecord>,
    #[deku(count = "header.resource_entries")]
    pub resources: Vec<DnsRecord>,
}

Here is the code, but deku::input_bits only referencing to the remaining bits:

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, DekuRead)]
#[deku(endian = "endian", ctx = "endian: deku::ctx::Endian")]
pub struct DnsRecord {
    #[deku(
        reader = "DnsRecord::read_domain(deku::input_bits, deku::bit_offset)",
    )]
    domain: String,
    typ: u16,
    class: u16,
    ttl: u32,
    rd_length: u16,
    #[deku(count = "rd_length")]
    r_data: Vec<u8>,
}

impl DnsRecord {
    fn read_domain(
        input: &BitSlice<Msb0, u8>,
        bit_offset: usize,
    ) -> Result<(&BitSlice<Msb0, u8>, String), DekuError> {
        let mut name = String::new();
        let mut pos = bit_offset;
        let mut delim = "";

        println!("{:x?}", input.as_raw_slice());

        loop {
            if input[pos] && input[pos + 1] {
                let offset = input[pos + 2..pos + 16].load_be();
                pos = offset;

                continue;
            } else {
                let next: usize = input[pos..pos + 8].load();
                pos += 8;

                if next == 0 {
                    return Ok((&input[pos..], name));
                } else {
                    name.push_str(delim);
                    (pos..pos + next * 8).step_by(8).for_each(|i| {
                        name.push(input[i..i + 8].load::<u8>() as char);
                    });
                    delim = ".";

                    pos += next * 8;
                }
            }
        }
    }
}
sharksforarms commented 2 years ago

Unfortunately, I don't think there's a good way of doing this at the moment.

At the moment the input bits is relative to the current position, there's not currently a way to look back

#[derive(Clone, Debug, Default, DekuRead, DekuWrite)]
#[deku(endian = "big")]
pub struct DnsPacket {
    // ...
    #[deku(reader = "dns_record_reader(deku::input_bits, deku::bit_offset, header.resource_entries)")]
    pub resources: Vec<DnsRecord>,
}

fn dns_record_reader(
        input: &BitSlice<Msb0, u8>,
        bit_offset: usize,
        count: usize,
    ) -> Result<(&BitSlice<Msb0, u8>, String), DekuError> {

     // read `count` DnsRecord from `input`, while passing `input` to the reader fn of DnsRecord

}
sharksforarms commented 2 years ago

Closing for now