#![allow(unused)] use std::{net::Ipv4Addr, path::Path}; use nom::{ bytes::complete::{take, take_until}, number::complete::{be_u16, be_u32, be_u8}, IResult, }; #[derive(Debug)] struct DhcpMessage<'a> { operation: Operation, hardware_type: HardwareType, hardware_address_length: u8, hops: u8, transaction_id: u32, seconds: u16, flags: Flags, client_address: Ipv4Addr, server_address: Ipv4Addr, gateway_address: Ipv4Addr, client_hardware_address: &'a [u8], server_name: &'a str, file_name: &'a Path, } #[derive(Debug)] struct Flags(u16); #[derive(Debug)] enum Operation { Request = 1, Reply = 2, } impl From for Operation { fn from(value: u8) -> Self { match value { 1 => Operation::Request, 2 => Operation::Reply, _ => todo!(), } } } #[derive(Debug)] enum HardwareType { Reserved(u8), Unassigned(u8), Ethernet, } impl From for HardwareType { fn from(value: u8) -> Self { use HardwareType::*; match value { a @ 0 => Reserved(a), 1 => Ethernet, v => Unassigned(v), } } } fn parse_dhcp_operation(i: &[u8]) -> IResult<&[u8], Operation> { be_u8(i).map(|(i, v)| (i, v.into())) } fn parse_dhcp_flags(i: &[u8]) -> IResult<&[u8], Flags> { be_u16(i).map(|(i, v)| (i, Flags(v))) } fn parse_dhcp_hardware_type(i: &[u8]) -> IResult<&[u8], HardwareType> { be_u8(i).map(|(i, v)| (i, v.into())) } fn parse_ipv4_address(i: &[u8]) -> IResult<&[u8], Ipv4Addr> { let (i, val) = take(4usize)(i)?; let ip = Ipv4Addr::new(val[0], val[1], val[2], val[3]); Ok((i, ip)) } fn parse_client_hardware_address(i: &[u8], hardware_address_length: u8) -> IResult<&[u8], &[u8]> { let (i, val) = take(hardware_address_length)(i)?; let (i, _padding) = take(0x10 - hardware_address_length)(i)?; Ok((i, val)) } fn parse_server_name(i: &[u8]) -> IResult<&[u8], &str> { let nullbyte_position = i.iter().position(|v| *v == b'\0').unwrap_or(64); let val = unsafe { std::str::from_utf8_unchecked(&i[..nullbyte_position]) }; Ok((i, val)) } fn parse_file(i: &[u8]) -> IResult<&[u8], &Path> { let nullbyte_position = i.iter().position(|v| *v == b'\0').unwrap_or(128); let val = unsafe { std::str::from_utf8_unchecked(&i[..nullbyte_position]) }; Ok((i, Path::new(val))) } fn parse_dhcp_packet(packet: &[u8]) -> IResult<&[u8], DhcpMessage> { let (packet, operation) = parse_dhcp_operation(packet)?; let (packet, hardware_type) = parse_dhcp_hardware_type(packet)?; let (packet, hardware_address_length) = be_u8(packet)?; let (packet, hops) = be_u8(packet)?; let (packet, transaction_id) = be_u32(packet)?; let (packet, seconds) = be_u16(packet)?; let (packet, flags) = parse_dhcp_flags(packet)?; let (packet, client_address) = parse_ipv4_address(packet)?; let (packet, server_address) = parse_ipv4_address(packet)?; let (packet, gateway_address) = parse_ipv4_address(packet)?; let (packet, client_hardware_address) = parse_client_hardware_address(packet, hardware_address_length)?; let (packet, server_name_bytes) = take(64usize)(packet)?; let (packet, file_name_bytes) = take(128usize)(packet)?; let (_, server_name) = parse_server_name(server_name_bytes)?; let (_, file_name) = parse_file(file_name_bytes)?; Ok(( packet, DhcpMessage { operation, hardware_type, hardware_address_length, hops, transaction_id, seconds, flags, client_address, server_address, gateway_address, client_hardware_address, server_name, file_name, }, )) } #[cfg(test)] mod tests { #[rustfmt::skip] static DHCP_PACKET: &'static [u8] = &[ 0x01, // op 0x01, // htype 0x06, // hlen 0x05, // hops 0x00, 0x00, 0x00, 0x01, // xid 0x00, 0x02, // secs 0x00, 0x01, // flags 0x7f, 0x00, 0x00, 0x01, // ciaddr 0x7f, 0x00, 0x00, 0x01, // siaddr 0x7f, 0x00, 0x00, 0x01, // giaddr // chaddr 0x54, 0x05, 0xdb, 0xa6, 0x96, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // server name 0x49, 0x20, 0x62, 0x69, 0x6D, 0x73, 0x20, 0x31, 0x20, 0x44, 0x48, 0x43, 0x50, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // file 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[test] fn parses() { let res = super::parse_dhcp_packet(DHCP_PACKET).unwrap(); dbg!(res); } }