| 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[..]); |
| } |
| } |