462 lines
15 KiB
Rust
462 lines
15 KiB
Rust
use byteorder::{ByteOrder, NetworkEndian};
|
|
use core::fmt;
|
|
|
|
use crate::{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, 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 fn SHA(hardware_len: u8, _protocol_len: u8) -> Field {
|
|
let start = OPER.end;
|
|
start..(start + hardware_len as usize)
|
|
}
|
|
|
|
#[inline]
|
|
pub 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 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 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 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::Truncated)` 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::Truncated)
|
|
} else if len < field::TPA(self.hardware_len(), self.protocol_len()).end {
|
|
Err(Error::Truncated)
|
|
} 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::Unrecognized)` 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::Unrecognized),
|
|
}
|
|
}
|
|
|
|
/// Return the length of a packet that will be emitted from this high-level representation.
|
|
pub 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={}/{} tgt={}/{} op={:?}",
|
|
source_hardware_addr,
|
|
source_protocol_addr,
|
|
target_hardware_addr,
|
|
target_protocol_addr,
|
|
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[..]);
|
|
}
|
|
}
|