use crate::misc::bytes_to_mac;
use core::{fmt, str::from_utf8};
pub const ETHERNET_MIN_HEADER_LENGTH: usize = 14;
pub const ETHERNET_MIN_FRAME_LENGTH: usize = 64;
pub const VLAN_TAG_LENGTH: usize = 4;
pub const ETHERTYPE_VLAN: u16 = 0x8100;
pub const ETHERTYPE_QINQ: u16 = 0x88A8;
pub struct EthernetWriter<'a> {
pub bytes: &'a mut [u8],
header_len: usize,
}
impl<'a> EthernetWriter<'a> {
#[inline]
pub fn new(bytes: &'a mut [u8]) -> Result<Self, &'static str> {
if bytes.len() < ETHERNET_MIN_HEADER_LENGTH {
return Err("Slice is too short to contain an Ethernet frame.");
}
Ok(Self {
bytes,
header_len: ETHERNET_MIN_HEADER_LENGTH,
})
}
#[inline]
pub fn header_len(&self) -> usize {
self.header_len
}
#[inline]
pub fn set_dest_mac(&mut self, dest: &[u8; 6]) {
self.bytes[0] = dest[0];
self.bytes[1] = dest[1];
self.bytes[2] = dest[2];
self.bytes[3] = dest[3];
self.bytes[4] = dest[4];
self.bytes[5] = dest[5];
}
#[inline]
pub fn set_src_mac(&mut self, src: &[u8; 6]) {
self.bytes[6] = src[0];
self.bytes[7] = src[1];
self.bytes[8] = src[2];
self.bytes[9] = src[3];
self.bytes[10] = src[4];
self.bytes[11] = src[5];
}
#[inline]
pub fn set_ethertype(&mut self, ethertype: u16) {
let offset = self.header_len - ETHERNET_MIN_HEADER_LENGTH;
self.bytes[12 + offset] = (ethertype >> 8) as u8;
self.bytes[13 + offset] = (ethertype & 0xFF) as u8;
}
#[inline]
pub fn set_vlan_tag(&mut self, tpid: u16, tci: u16) -> Result<(), &'static str> {
if self.bytes.len() < self.header_len + VLAN_TAG_LENGTH {
return Err("Slice is too short to contain VLAN tagging.");
}
self.bytes[12] = (tpid >> 8) as u8;
self.bytes[13] = (tpid & 0xFF) as u8;
self.bytes[14] = (tci >> 8) as u8;
self.bytes[15] = (tci & 0xFF) as u8;
self.header_len += VLAN_TAG_LENGTH;
Ok(())
}
#[inline]
pub fn set_double_vlan_tag(
&mut self,
outer_tpid: u16,
outer_tci: u16,
inner_tpid: u16,
inner_tci: u16,
) -> Result<(), &'static str> {
if self.bytes.len() < self.header_len + 2 * VLAN_TAG_LENGTH {
return Err("Slice is too short to contain double VLAN tagging.");
}
self.bytes[12] = (outer_tpid >> 8) as u8;
self.bytes[13] = (outer_tpid & 0xFF) as u8;
self.bytes[14] = (outer_tci >> 8) as u8;
self.bytes[15] = (outer_tci & 0xFF) as u8;
self.bytes[16] = (inner_tpid >> 8) as u8;
self.bytes[17] = (inner_tpid & 0xFF) as u8;
self.bytes[18] = (inner_tci >> 8) as u8;
self.bytes[19] = (inner_tci & 0xFF) as u8;
self.header_len += 2 * VLAN_TAG_LENGTH;
Ok(())
}
}
#[derive(PartialEq)]
pub struct EthernetReader<'a> {
pub bytes: &'a [u8],
header_len: usize,
}
impl<'a> EthernetReader<'a> {
#[inline]
pub fn new(bytes: &'a [u8]) -> Result<Self, &'static str> {
if bytes.len() < ETHERNET_MIN_HEADER_LENGTH {
return Err("Slice is too short to contain an Ethernet frame.");
}
let header_len = Self::calculate_header_len(bytes)?;
Ok(Self { bytes, header_len })
}
#[inline]
pub fn calculate_header_len(bytes: &[u8]) -> Result<usize, &'static str> {
match ((bytes[12] as u16) << 8) | (bytes[13] as u16) {
ETHERTYPE_VLAN => {
if bytes.len() < ETHERNET_MIN_HEADER_LENGTH + VLAN_TAG_LENGTH {
return Err("Slice is too short to contain VLAN tagging.");
}
Ok(ETHERNET_MIN_HEADER_LENGTH + VLAN_TAG_LENGTH)
}
ETHERTYPE_QINQ => {
if bytes.len() < ETHERNET_MIN_HEADER_LENGTH + 2 * VLAN_TAG_LENGTH {
return Err("Slice is too short to contain double VLAN tagging.");
}
if ((bytes[16] as u16) << 8) | (bytes[17] as u16) != ETHERTYPE_VLAN {
return Err("Invalid double VLAN tag.");
}
Ok(ETHERNET_MIN_HEADER_LENGTH + 2 * VLAN_TAG_LENGTH)
}
_ => Ok(ETHERNET_MIN_HEADER_LENGTH),
}
}
#[inline]
pub fn is_vlan_tagged(bytes: &[u8]) -> bool {
((bytes[12] as u16) << 8) | (bytes[13] as u16) == ETHERTYPE_VLAN
}
#[inline]
pub fn is_vlan_double_tagged(bytes: &[u8]) -> bool {
((bytes[12] as u16) << 8) | (bytes[13] as u16) == ETHERTYPE_QINQ
}
#[inline]
pub fn dest_mac(&self) -> &[u8] {
&self.bytes[0..6]
}
#[inline]
pub fn src_mac(&self) -> &[u8] {
&self.bytes[6..12]
}
#[inline]
pub fn ethertype(&self) -> u16 {
let offset = self.header_len - ETHERNET_MIN_HEADER_LENGTH;
((self.bytes[12 + offset] as u16) << 8) | (self.bytes[13 + offset] as u16)
}
#[inline]
pub fn vlan_tag(&self) -> Option<(u16, u16)> {
if !Self::is_vlan_tagged(self.bytes) {
return None;
}
let tpid = ((self.bytes[12] as u16) << 8) | (self.bytes[13] as u16);
let tci = ((self.bytes[14] as u16) << 8) | (self.bytes[15] as u16);
Some((tpid, tci))
}
#[inline]
pub fn double_vlan_tag(&self) -> Option<((u16, u16), (u16, u16))> {
if !Self::is_vlan_double_tagged(self.bytes) {
return None;
}
let outer_tpid = ((self.bytes[12] as u16) << 8) | (self.bytes[13] as u16);
let outer_tci = ((self.bytes[14] as u16) << 8) | (self.bytes[15] as u16);
let inner_tpid = ((self.bytes[16] as u16) << 8) | (self.bytes[17] as u16);
let inner_tci = ((self.bytes[18] as u16) << 8) | (self.bytes[19] as u16);
Some(((outer_tpid, outer_tci), (inner_tpid, inner_tci)))
}
#[inline]
pub const fn header_len(&self) -> usize {
self.header_len
}
#[inline]
pub fn header(&self) -> &'a [u8] {
&self.bytes[..self.header_len]
}
#[inline]
pub fn payload(&self) -> &[u8] {
&self.bytes[self.header_len..]
}
}
impl fmt::Debug for EthernetReader<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d_buf = [0u8; 18];
let d_len = bytes_to_mac(self.dest_mac(), &mut d_buf);
let d_hex = from_utf8(&d_buf[..d_len]).unwrap();
let mut s_buf = [0u8; 18];
let s_len = bytes_to_mac(self.src_mac(), &mut s_buf);
let s_hex = from_utf8(&s_buf[..s_len]).unwrap();
f.debug_struct("EthernetFrame")
.field("dest_mac", &d_hex)
.field("src_mac", &s_hex)
.field("ethertype", &self.ethertype())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn getters_and_setters() {
let mut bytes = [0u8; 14];
let src = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
let dest = [0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b];
let ethertype = 2048;
let mut writer = EthernetWriter::new(&mut bytes).unwrap();
writer.set_src_mac(&src);
writer.set_dest_mac(&dest);
writer.set_ethertype(ethertype);
let reader = EthernetReader::new(&bytes).unwrap();
assert_eq!(reader.src_mac(), src);
assert_eq!(reader.dest_mac(), dest);
assert_eq!(reader.ethertype(), ethertype);
}
}