210 lines
6.0 KiB
Rust
210 lines
6.0 KiB
Rust
|
|
//! Ethernet frame, copied from SmolTCP,
|
||
|
|
//! [here](https://github.com/smoltcp-rs/smoltcp/blob/53caf70f640d5ccb3cd1492e1cb178bc7dfa3cdd/src/wire/ethernet.rs)
|
||
|
|
//! at time of writing.
|
||
|
|
//!
|
||
|
|
//! Then drastically modified/stripped down to suit EtherCrab's needs.
|
||
|
|
|
||
|
|
use core::fmt;
|
||
|
|
|
||
|
|
use crate::error::{Error, PduError};
|
||
|
|
|
||
|
|
/// A six-octet Ethernet II address.
|
||
|
|
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
||
|
|
pub struct EthernetAddress(pub [u8; 6]);
|
||
|
|
|
||
|
|
impl EthernetAddress {
|
||
|
|
/// The broadcast address.
|
||
|
|
pub const BROADCAST: EthernetAddress = EthernetAddress([0xff; 6]);
|
||
|
|
|
||
|
|
/// Construct an Ethernet address from a sequence of octets, in big-endian.
|
||
|
|
///
|
||
|
|
/// # Panics
|
||
|
|
/// The function panics if `data` is not six octets long.
|
||
|
|
pub fn from_bytes(data: &[u8]) -> EthernetAddress {
|
||
|
|
let mut bytes = [0; 6];
|
||
|
|
bytes.copy_from_slice(data);
|
||
|
|
EthernetAddress(bytes)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return an Ethernet address as a sequence of octets, in big-endian.
|
||
|
|
pub const fn as_bytes(&self) -> &[u8] {
|
||
|
|
&self.0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for EthernetAddress {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
|
let bytes = self.0;
|
||
|
|
write!(
|
||
|
|
f,
|
||
|
|
"{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
|
||
|
|
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(feature = "defmt")]
|
||
|
|
impl defmt::Format for EthernetAddress {
|
||
|
|
fn format(&self, fmt: defmt::Formatter) {
|
||
|
|
let bytes = self.0;
|
||
|
|
defmt::write!(
|
||
|
|
fmt,
|
||
|
|
"{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
|
||
|
|
bytes[0],
|
||
|
|
bytes[1],
|
||
|
|
bytes[2],
|
||
|
|
bytes[3],
|
||
|
|
bytes[4],
|
||
|
|
bytes[5]
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A read/write wrapper around an Ethernet II frame buffer.
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||
|
|
pub struct EthernetFrame<T: AsRef<[u8]>> {
|
||
|
|
buffer: T,
|
||
|
|
}
|
||
|
|
|
||
|
|
mod field {
|
||
|
|
use core::ops::{Range, RangeFrom};
|
||
|
|
|
||
|
|
pub const DESTINATION: Range<usize> = 0..6;
|
||
|
|
pub const SOURCE: Range<usize> = 6..12;
|
||
|
|
pub const ETHERTYPE: Range<usize> = 12..14;
|
||
|
|
pub const PAYLOAD: RangeFrom<usize> = 14..;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The Ethernet header length
|
||
|
|
pub const ETHERNET_HEADER_LEN: usize = field::PAYLOAD.start;
|
||
|
|
|
||
|
|
impl<T: AsRef<[u8]>> EthernetFrame<T> {
|
||
|
|
/// Imbue a raw octet buffer with Ethernet frame structure.
|
||
|
|
pub const fn new_unchecked(buffer: T) -> EthernetFrame<T> {
|
||
|
|
EthernetFrame { 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<EthernetFrame<T>, Error> {
|
||
|
|
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.
|
||
|
|
pub fn check_len(&self) -> Result<(), Error> {
|
||
|
|
let len = self.buffer.as_ref().len();
|
||
|
|
if len < ETHERNET_HEADER_LEN {
|
||
|
|
Err(Error::Pdu(PduError::Ethernet))
|
||
|
|
} else {
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Consumes the frame, returning the underlying buffer.
|
||
|
|
pub fn into_inner(self) -> T {
|
||
|
|
self.buffer
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return the length of a frame header.
|
||
|
|
pub const fn header_len() -> usize {
|
||
|
|
ETHERNET_HEADER_LEN
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return the length of a buffer required to hold a packet with the payload
|
||
|
|
/// of a given length.
|
||
|
|
pub const fn buffer_len(payload_len: usize) -> usize {
|
||
|
|
ETHERNET_HEADER_LEN + payload_len
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return the destination address field.
|
||
|
|
#[inline]
|
||
|
|
pub fn dst_addr(&self) -> EthernetAddress {
|
||
|
|
let data = self.buffer.as_ref();
|
||
|
|
EthernetAddress::from_bytes(&data[field::DESTINATION])
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return the source address field.
|
||
|
|
#[inline]
|
||
|
|
pub fn src_addr(&self) -> EthernetAddress {
|
||
|
|
let data = self.buffer.as_ref();
|
||
|
|
EthernetAddress::from_bytes(&data[field::SOURCE])
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return the EtherType field, without checking for 802.1Q.
|
||
|
|
#[inline]
|
||
|
|
pub fn ethertype(&self) -> u16 {
|
||
|
|
let data = self.buffer.as_ref();
|
||
|
|
|
||
|
|
// Ethernet is big-endian
|
||
|
|
data.get(field::ETHERTYPE)
|
||
|
|
.map(|res| u16::from_be_bytes(res.try_into().unwrap()))
|
||
|
|
// EtherCrab only really cares whether the ethertype is 0x88a4, so defaulting to zero on
|
||
|
|
// unparseable ethertypes is fine here (imo, lol)
|
||
|
|
.unwrap_or(0)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<'a, T: AsRef<[u8]> + ?Sized> EthernetFrame<&'a T> {
|
||
|
|
/// Return a pointer to the payload, without checking for 802.1Q.
|
||
|
|
#[inline]
|
||
|
|
pub fn payload(&self) -> &'a [u8] {
|
||
|
|
let data = self.buffer.as_ref();
|
||
|
|
&data[field::PAYLOAD]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<T: AsRef<[u8]> + AsMut<[u8]>> EthernetFrame<T> {
|
||
|
|
/// Set the destination address field.
|
||
|
|
#[inline]
|
||
|
|
pub fn set_dst_addr(&mut self, value: EthernetAddress) {
|
||
|
|
let data = self.buffer.as_mut();
|
||
|
|
data[field::DESTINATION].copy_from_slice(value.as_bytes())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Set the source address field.
|
||
|
|
#[inline]
|
||
|
|
pub fn set_src_addr(&mut self, value: EthernetAddress) {
|
||
|
|
let data = self.buffer.as_mut();
|
||
|
|
data[field::SOURCE].copy_from_slice(value.as_bytes())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Set the EtherType field.
|
||
|
|
#[inline]
|
||
|
|
pub fn set_ethertype(&mut self, value: u16) {
|
||
|
|
let data = self.buffer.as_mut();
|
||
|
|
|
||
|
|
data[field::ETHERTYPE].copy_from_slice(&value.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return a mutable pointer to the payload.
|
||
|
|
#[inline]
|
||
|
|
pub fn payload_mut(&mut self) -> &mut [u8] {
|
||
|
|
let data = self.buffer.as_mut();
|
||
|
|
&mut data[field::PAYLOAD]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<T: AsRef<[u8]>> AsRef<[u8]> for EthernetFrame<T> {
|
||
|
|
fn as_ref(&self) -> &[u8] {
|
||
|
|
self.buffer.as_ref()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<T: AsRef<[u8]>> fmt::Display for EthernetFrame<T> {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
|
write!(
|
||
|
|
f,
|
||
|
|
"EthernetII src={} dst={} type={}",
|
||
|
|
self.src_addr(),
|
||
|
|
self.dst_addr(),
|
||
|
|
self.ethertype()
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|