network-types 0.2.0

Rust structs representing network-related types in Linux.
Documentation
use core::{error::Error, fmt, mem};

use num_traits::FromPrimitive as _;

/// Represents errors that occur while processing Ethernet headers.
#[derive(Debug, Eq, PartialEq)]
pub enum EthernetError {
    /// Invalid tag of an encapsulated protocol.
    InvalidEtherType(u16),
}

impl EthernetError {
    pub fn msg_and_code(&self) -> (&'static str, u16) {
        match self {
            Self::InvalidEtherType(id) => ("invalid ID of an encapsulated protocol", *id),
        }
    }
}

impl fmt::Display for EthernetError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (msg, code) = self.msg_and_code();
        write!(f, "{msg}: {code}")
    }
}

impl Error for EthernetError {}

/// Ethernet header structure that appears at the beginning of every Ethernet frame.
/// This structure represents the standard IEEE 802.3 Ethernet header format.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
pub struct EthHdr {
    /// Destination MAC address.
    pub dst_addr: [u8; 6],
    /// Source MAC address.
    pub src_addr: [u8; 6],
    /// Protocol which is encapsulated in the payload of the frame.
    /// Indicates what type of data follows the Ethernet header (e.g., IPv4, IPv6, ARP)
    pub ether_type: u16,
}

impl EthHdr {
    pub const LEN: usize = mem::size_of::<EthHdr>();

    /// Attempts to convert the raw ether_type field into an EtherType enum.
    /// Returns either the corresponding EtherType variant or the raw value if unknown.
    ///
    /// # Returns
    /// - `Ok(EtherType)` if a known protocol type
    /// - `Err(u16)` if an unknown protocol type (returns the raw value)
    pub fn ether_type(&self) -> Result<EtherType, EthernetError> {
        EtherType::from_u16(self.ether_type).ok_or(EthernetError::InvalidEtherType(self.ether_type))
    }

    /// Creates a new Ethernet header with the specified addresses and protocol type
    ///
    /// # Parameters
    /// - `dst_addr`: The destination MAC address
    /// - `src_addr`: The source MAC address
    /// - `ether_type_enum`: The protocol type encapsulated in the payload
    ///
    /// # Returns
    /// A new EthHdr structure initialized with the given values
    pub fn new(dst_addr: [u8; 6], src_addr: [u8; 6], eth_type: EtherType) -> Self {
        EthHdr {
            dst_addr,
            src_addr,
            ether_type: eth_type.into(),
        }
    }
}

/// Protocol which is encapsulated in the payload of the Ethernet frame.
/// These values represent the standard IEEE assigned protocol numbers
#[repr(u16)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, num_derive::FromPrimitive, num_derive::ToPrimitive)]
#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
pub enum EtherType {
    Loop = 0x0060_u16.to_be(),
    Ipv4 = 0x0800_u16.to_be(),
    Arp = 0x0806_u16.to_be(),
    Ieee8021q = 0x8100_u16.to_be(),
    Ipv6 = 0x86DD_u16.to_be(),
    Ieee8021ad = 0x88A8_u16.to_be(),
    Ieee8021MacSec = 0x88E5_u16.to_be(),
    Ieee8021ah = 0x88E7_u16.to_be(),
    Ieee8021mvrp = 0x88F5_u16.to_be(),
    FibreChannel = 0x8906_u16.to_be(),
    Infiniband = 0x8915_u16.to_be(),
    LoopbackIeee8023 = 0x9000_u16.to_be(),
    Ieee8021QinQ1 = 0x9100_u16.to_be(),
    Ieee8021QinQ2 = 0x9200_u16.to_be(),
    Ieee8021QinQ3 = 0x9300_u16.to_be(),
}

/// [`num_traits::ToPrimitive::to_u16`] returns an [`Option`], but since
/// [`IpProto`] is `#[repr(u16)]`, it will never return `None`. Provide an
/// infallible alternative for convenience.
impl From<EtherType> for u16 {
    fn from(ether_type: EtherType) -> Self {
        ether_type as u16
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use assert_matches::assert_matches;
    use core::mem;

    // Test constants for MAC addresses
    const TEST_DST_MAC: [u8; 6] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
    const TEST_SRC_MAC: [u8; 6] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];

    #[test]
    fn test_ethhdr_len() {
        assert_eq!(EthHdr::LEN, 14);
        assert_eq!(mem::size_of::<EthHdr>(), 14);
    }

    #[test]
    fn test_ethhdr_new() {
        let eth_hdr = EthHdr::new(TEST_DST_MAC, TEST_SRC_MAC, EtherType::Ipv4);
        assert_eq!(eth_hdr.dst_addr, TEST_DST_MAC);
        assert_eq!(eth_hdr.src_addr, TEST_SRC_MAC);
        let ether_type_value = eth_hdr.ether_type;
        assert_eq!(ether_type_value, EtherType::Ipv4 as u16);
        assert_eq!(ether_type_value, 0x0800_u16.to_be());
    }

    #[test]
    fn test_ethhdr_ether_type_method_known() {
        let eth_hdr = EthHdr {
            dst_addr: TEST_DST_MAC,
            src_addr: TEST_SRC_MAC,
            ether_type: EtherType::Ipv6 as u16,
        };
        assert_eq!(eth_hdr.ether_type().unwrap(), EtherType::Ipv6);
    }

    #[test]
    fn test_ethhdr_ether_type_method_unknown() {
        let unknown_type_val = 0x1234_u16;
        let eth_hdr = EthHdr {
            dst_addr: TEST_DST_MAC,
            src_addr: TEST_SRC_MAC,
            ether_type: unknown_type_val.to_be(),
        };
        assert_matches!(eth_hdr.ether_type(), Err(EthernetError::InvalidEtherType(val)) if val == unknown_type_val.to_be());
    }

    #[test]
    fn test_ethertype_from_u16_known() {
        let ipv4_val = 0x0800_u16.to_be();
        assert_eq!(EtherType::from_u16(ipv4_val), Some(EtherType::Ipv4));

        let ipv6_val = 0x86DD_u16.to_be();
        assert_eq!(EtherType::from_u16(ipv6_val), Some(EtherType::Ipv6));

        let arp_val = 0x0806_u16.to_be();
        assert_eq!(EtherType::from_u16(arp_val), Some(EtherType::Arp));
    }

    #[test]
    fn test_ethertype_try_from_u16_unknown() {
        let unknown_val = 0x1234_u16.to_be();
        assert_eq!(EtherType::from_u16(unknown_val), None);
    }

    #[test]
    fn test_u16_from_ethertype() {
        assert_eq!(u16::from(EtherType::Ipv4), 0x0800_u16.to_be());
        assert_eq!(u16::from(EtherType::Arp), 0x0806_u16.to_be());
        assert_eq!(u16::from(EtherType::Ipv6), 0x86DD_u16.to_be());
        assert_eq!(u16::from(EtherType::Loop), 0x0060_u16.to_be());
    }

    #[test]
    fn test_ethertype_variants_unique_values() {
        let all_types = [
            EtherType::Loop,
            EtherType::Ipv4,
            EtherType::Arp,
            EtherType::Ieee8021q,
            EtherType::Ipv6,
            EtherType::Ieee8021ad,
            EtherType::Ieee8021MacSec,
            EtherType::Ieee8021ah,
            EtherType::Ieee8021mvrp,
            EtherType::FibreChannel,
            EtherType::Infiniband,
            EtherType::LoopbackIeee8023,
            EtherType::Ieee8021QinQ1,
            EtherType::Ieee8021QinQ2,
            EtherType::Ieee8021QinQ3,
        ];

        for i in 0..all_types.len() {
            for j in (i + 1)..all_types.len() {
                // Compare the u16 representation of each EtherType
                let val_i = all_types[i] as u16;
                let val_j = all_types[j] as u16;
                assert_ne!(
                    val_i, val_j,
                    "Duplicate EtherType value found: {:?} and {:?} both have value {:#06x}",
                    all_types[i], all_types[j], val_i
                );
            }
        }
    }
}