summaryrefslogtreecommitdiff
path: root/src/wire/arp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/wire/arp.rs')
-rw-r--r--src/wire/arp.rs458
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[..]);
+ }
+}