diff options
Diffstat (limited to 'src/wire/arp.rs')
-rw-r--r-- | src/wire/arp.rs | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/src/wire/arp.rs b/src/wire/arp.rs new file mode 100644 index 0000000..bb0df3a --- /dev/null +++ b/src/wire/arp.rs @@ -0,0 +1,458 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; + +pub use super::EthernetProtocol as Protocol; + +enum_with_unknown! { + /// ARP hardware type. + pub enum Hardware(u16) { + Ethernet = 1 + } +} + +enum_with_unknown! { + /// ARP operation type. + pub enum Operation(u16) { + Request = 1, + Reply = 2 + } +} + +/// A read/write wrapper around an Address Resolution Protocol packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet<T: AsRef<[u8]>> { + buffer: T, +} + +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + pub const HTYPE: Field = 0..2; + pub const PTYPE: Field = 2..4; + pub const HLEN: usize = 4; + pub const PLEN: usize = 5; + pub const OPER: Field = 6..8; + + #[inline] + pub const fn SHA(hardware_len: u8, _protocol_len: u8) -> Field { + let start = OPER.end; + start..(start + hardware_len as usize) + } + + #[inline] + pub const fn SPA(hardware_len: u8, protocol_len: u8) -> Field { + let start = SHA(hardware_len, protocol_len).end; + start..(start + protocol_len as usize) + } + + #[inline] + pub const fn THA(hardware_len: u8, protocol_len: u8) -> Field { + let start = SPA(hardware_len, protocol_len).end; + start..(start + hardware_len as usize) + } + + #[inline] + pub const fn TPA(hardware_len: u8, protocol_len: u8) -> Field { + let start = THA(hardware_len, protocol_len).end; + start..(start + protocol_len as usize) + } +} + +impl<T: AsRef<[u8]>> Packet<T> { + /// Imbue a raw octet buffer with ARP packet structure. + pub const fn new_unchecked(buffer: T) -> Packet<T> { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result<Packet<T>> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_hardware_len] or + /// [set_protocol_len]. + /// + /// [set_hardware_len]: #method.set_hardware_len + /// [set_protocol_len]: #method.set_protocol_len + #[allow(clippy::if_same_then_else)] + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::OPER.end { + Err(Error) + } else if len < field::TPA(self.hardware_len(), self.protocol_len()).end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the hardware type field. + #[inline] + pub fn hardware_type(&self) -> Hardware { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::HTYPE]); + Hardware::from(raw) + } + + /// Return the protocol type field. + #[inline] + pub fn protocol_type(&self) -> Protocol { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::PTYPE]); + Protocol::from(raw) + } + + /// Return the hardware length field. + #[inline] + pub fn hardware_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::HLEN] + } + + /// Return the protocol length field. + #[inline] + pub fn protocol_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::PLEN] + } + + /// Return the operation field. + #[inline] + pub fn operation(&self) -> Operation { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::OPER]); + Operation::from(raw) + } + + /// Return the source hardware address field. + pub fn source_hardware_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::SHA(self.hardware_len(), self.protocol_len())] + } + + /// Return the source protocol address field. + pub fn source_protocol_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::SPA(self.hardware_len(), self.protocol_len())] + } + + /// Return the target hardware address field. + pub fn target_hardware_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::THA(self.hardware_len(), self.protocol_len())] + } + + /// Return the target protocol address field. + pub fn target_protocol_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::TPA(self.hardware_len(), self.protocol_len())] + } +} + +impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> { + /// Set the hardware type field. + #[inline] + pub fn set_hardware_type(&mut self, value: Hardware) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::HTYPE], value.into()) + } + + /// Set the protocol type field. + #[inline] + pub fn set_protocol_type(&mut self, value: Protocol) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::PTYPE], value.into()) + } + + /// Set the hardware length field. + #[inline] + pub fn set_hardware_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::HLEN] = value + } + + /// Set the protocol length field. + #[inline] + pub fn set_protocol_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::PLEN] = value + } + + /// Set the operation field. + #[inline] + pub fn set_operation(&mut self, value: Operation) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::OPER], value.into()) + } + + /// Set the source hardware address field. + /// + /// # Panics + /// The function panics if `value` is not `self.hardware_len()` long. + pub fn set_source_hardware_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::SHA(hardware_len, protocol_len)].copy_from_slice(value) + } + + /// Set the source protocol address field. + /// + /// # Panics + /// The function panics if `value` is not `self.protocol_len()` long. + pub fn set_source_protocol_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::SPA(hardware_len, protocol_len)].copy_from_slice(value) + } + + /// Set the target hardware address field. + /// + /// # Panics + /// The function panics if `value` is not `self.hardware_len()` long. + pub fn set_target_hardware_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::THA(hardware_len, protocol_len)].copy_from_slice(value) + } + + /// Set the target protocol address field. + /// + /// # Panics + /// The function panics if `value` is not `self.protocol_len()` long. + pub fn set_target_protocol_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::TPA(hardware_len, protocol_len)].copy_from_slice(value) + } +} + +impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +use crate::wire::{EthernetAddress, Ipv4Address}; + +/// A high-level representation of an Address Resolution Protocol packet. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Repr { + /// An Ethernet and IPv4 Address Resolution Protocol packet. + EthernetIpv4 { + operation: Operation, + source_hardware_addr: EthernetAddress, + source_protocol_addr: Ipv4Address, + target_hardware_addr: EthernetAddress, + target_protocol_addr: Ipv4Address, + }, +} + +impl Repr { + /// Parse an Address Resolution Protocol packet and return a high-level representation, + /// or return `Err(Error)` if the packet is not recognized. + pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr> { + match ( + packet.hardware_type(), + packet.protocol_type(), + packet.hardware_len(), + packet.protocol_len(), + ) { + (Hardware::Ethernet, Protocol::Ipv4, 6, 4) => Ok(Repr::EthernetIpv4 { + operation: packet.operation(), + source_hardware_addr: EthernetAddress::from_bytes(packet.source_hardware_addr()), + source_protocol_addr: Ipv4Address::from_bytes(packet.source_protocol_addr()), + target_hardware_addr: EthernetAddress::from_bytes(packet.target_hardware_addr()), + target_protocol_addr: Ipv4Address::from_bytes(packet.target_protocol_addr()), + }), + _ => Err(Error), + } + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + match *self { + Repr::EthernetIpv4 { .. } => field::TPA(6, 4).end, + } + } + + /// Emit a high-level representation into an Address Resolution Protocol packet. + pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) { + match *self { + Repr::EthernetIpv4 { + operation, + source_hardware_addr, + source_protocol_addr, + target_hardware_addr, + target_protocol_addr, + } => { + packet.set_hardware_type(Hardware::Ethernet); + packet.set_protocol_type(Protocol::Ipv4); + packet.set_hardware_len(6); + packet.set_protocol_len(4); + packet.set_operation(operation); + packet.set_source_hardware_addr(source_hardware_addr.as_bytes()); + packet.set_source_protocol_addr(source_protocol_addr.as_bytes()); + packet.set_target_hardware_addr(target_hardware_addr.as_bytes()); + packet.set_target_protocol_addr(target_protocol_addr.as_bytes()); + } + } + } +} + +impl<T: AsRef<[u8]>> fmt::Display for Packet<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + _ => { + write!(f, "ARP (unrecognized)")?; + write!( + f, + " htype={:?} ptype={:?} hlen={:?} plen={:?} op={:?}", + self.hardware_type(), + self.protocol_type(), + self.hardware_len(), + self.protocol_len(), + self.operation() + )?; + write!( + f, + " sha={:?} spa={:?} tha={:?} tpa={:?}", + self.source_hardware_addr(), + self.source_protocol_addr(), + self.target_hardware_addr(), + self.target_protocol_addr() + )?; + Ok(()) + } + } + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::EthernetIpv4 { + operation, + source_hardware_addr, + source_protocol_addr, + target_hardware_addr, + target_protocol_addr, + } => { + write!( + f, + "ARP type=Ethernet+IPv4 src={source_hardware_addr}/{source_protocol_addr} tgt={target_hardware_addr}/{target_protocol_addr} op={operation:?}" + ) + } + } + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + match Packet::new_checked(buffer) { + Err(err) => write!(f, "{indent}({err})"), + Ok(packet) => write!(f, "{indent}{packet}"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + static PACKET_BYTES: [u8; 28] = [ + 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x21, + 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x41, 0x42, 0x43, 0x44, + ]; + + #[test] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(packet.hardware_type(), Hardware::Ethernet); + assert_eq!(packet.protocol_type(), Protocol::Ipv4); + assert_eq!(packet.hardware_len(), 6); + assert_eq!(packet.protocol_len(), 4); + assert_eq!(packet.operation(), Operation::Request); + assert_eq!( + packet.source_hardware_addr(), + &[0x11, 0x12, 0x13, 0x14, 0x15, 0x16] + ); + assert_eq!(packet.source_protocol_addr(), &[0x21, 0x22, 0x23, 0x24]); + assert_eq!( + packet.target_hardware_addr(), + &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36] + ); + assert_eq!(packet.target_protocol_addr(), &[0x41, 0x42, 0x43, 0x44]); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 28]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_hardware_type(Hardware::Ethernet); + packet.set_protocol_type(Protocol::Ipv4); + packet.set_hardware_len(6); + packet.set_protocol_len(4); + packet.set_operation(Operation::Request); + packet.set_source_hardware_addr(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]); + packet.set_source_protocol_addr(&[0x21, 0x22, 0x23, 0x24]); + packet.set_target_hardware_addr(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]); + packet.set_target_protocol_addr(&[0x41, 0x42, 0x43, 0x44]); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } + + fn packet_repr() -> Repr { + Repr::EthernetIpv4 { + operation: Operation::Request, + source_hardware_addr: EthernetAddress::from_bytes(&[ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + ]), + source_protocol_addr: Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]), + target_hardware_addr: EthernetAddress::from_bytes(&[ + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + ]), + target_protocol_addr: Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44]), + } + } + + #[test] + fn test_parse() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet).unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + fn test_emit() { + let mut bytes = vec![0xa5; 28]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet_repr().emit(&mut packet); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } +} |