//! K435: bounded IPv4 header geometry on a completed Ethernet frame. //! Header checksum and protocol delivery are separate gates. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { ShortBaseHeader, WrongVersion, ShortIhl, TruncatedHeader, TotalShorterThanHeader, TotalBeyondFrame, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ChecksumError { Geometry(Error), InvalidHeaderChecksum, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HeaderGeometry { pub header_bytes: usize, pub total_bytes: usize, } pub fn header_geometry(datagram: &[u8]) -> Result { if datagram.len() < 20 { return Err(Error::ShortBaseHeader); } if datagram[0] >> 4 != 4 { return Err(Error::WrongVersion); } let ihl = usize::from(datagram[0] & 0x0f); if ihl < 5 { return Err(Error::ShortIhl); } let header_bytes = ihl * 4; if datagram.len() < header_bytes { return Err(Error::TruncatedHeader); } let total_bytes = usize::from(u16::from_be_bytes([datagram[2], datagram[3]])); if total_bytes < header_bytes { return Err(Error::TotalShorterThanHeader); } if total_bytes > datagram.len() { return Err(Error::TotalBeyondFrame); } Ok(HeaderGeometry { header_bytes, total_bytes, }) } /// RFC 791 one's-complement sum covers only IHL header bytes, not payload or /// Ethernet padding. Geometry is checked before any indexed word reads. pub fn verify_header_checksum(datagram: &[u8]) -> Result { let geometry = header_geometry(datagram).map_err(ChecksumError::Geometry)?; let mut sum = 0u32; for word in datagram[..geometry.header_bytes].chunks_exact(2) { sum += u32::from(u16::from_be_bytes([word[0], word[1]])); } while sum > 0xffff { sum = (sum & 0xffff) + (sum >> 16); } if sum != 0xffff { return Err(ChecksumError::InvalidHeaderChecksum); } Ok(geometry) } #[cfg(test)] mod tests { use super::*; #[test] fn k435_ipv4_header_geometry_accepts_minimum_and_options() { let mut datagram = [0u8; 46]; datagram[0] = 0x45; datagram[2..4].copy_from_slice(&28u16.to_be_bytes()); assert_eq!( header_geometry(&datagram), Ok(HeaderGeometry { header_bytes: 20, total_bytes: 28 }) ); datagram[0] = 0x46; assert_eq!( header_geometry(&datagram), Ok(HeaderGeometry { header_bytes: 24, total_bytes: 28 }) ); } #[test] fn k435_ipv4_header_geometry_rejects_every_length_boundary() { let mut datagram = [0u8; 46]; datagram[0] = 0x45; datagram[2..4].copy_from_slice(&28u16.to_be_bytes()); assert_eq!( header_geometry(&datagram[..19]), Err(Error::ShortBaseHeader) ); datagram[0] = 0x65; assert_eq!(header_geometry(&datagram), Err(Error::WrongVersion)); datagram[0] = 0x44; assert_eq!(header_geometry(&datagram), Err(Error::ShortIhl)); datagram[0] = 0x4c; assert_eq!(header_geometry(&datagram), Err(Error::TruncatedHeader)); datagram[0] = 0x46; datagram[2..4].copy_from_slice(&23u16.to_be_bytes()); assert_eq!( header_geometry(&datagram), Err(Error::TotalShorterThanHeader) ); datagram[0] = 0x45; datagram[2..4].copy_from_slice(&47u16.to_be_bytes()); assert_eq!(header_geometry(&datagram), Err(Error::TotalBeyondFrame)); } #[test] fn k436_ipv4_header_checksum_accepts_known_header_and_ignores_payload() { let mut datagram = [0u8; 46]; datagram[..20].copy_from_slice(&[ 0x45, 0, 0, 28, 0, 1, 0, 0, 64, 17, 0xf6, 0xcc, 192, 0, 2, 1, 192, 0, 2, 2, ]); let expected = Ok(HeaderGeometry { header_bytes: 20, total_bytes: 28, }); assert_eq!(verify_header_checksum(&datagram), expected); datagram[20] ^= 1; assert_eq!(verify_header_checksum(&datagram), expected); datagram[45] ^= 1; assert_eq!(verify_header_checksum(&datagram), expected); } #[test] fn k436_ipv4_header_checksum_rejects_real_header_field_changes() { let mut datagram = [0u8; 46]; datagram[..20].copy_from_slice(&[ 0x45, 0, 0, 28, 0, 1, 0, 0, 64, 17, 0xf6, 0xcc, 192, 0, 2, 1, 192, 0, 2, 2, ]); for index in [8, 10, 12, 19] { datagram[index] ^= 1; assert_eq!( verify_header_checksum(&datagram), Err(ChecksumError::InvalidHeaderChecksum) ); datagram[index] ^= 1; } datagram[0] = 0x44; assert_eq!( verify_header_checksum(&datagram), Err(ChecksumError::Geometry(Error::ShortIhl)) ); } }