use std::fmt;
use net_lattice_core::{Error, Id, Result};
use crate::mac::MacAddress;
pub type InterfaceId = Id<Interface>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum InterfaceKind {
Ethernet,
Wireless,
Loopback,
PointToPoint,
Bridge,
Other(u32),
}
impl fmt::Display for InterfaceKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InterfaceKind::Ethernet => write!(f, "ethernet"),
InterfaceKind::Wireless => write!(f, "wireless"),
InterfaceKind::Loopback => write!(f, "loopback"),
InterfaceKind::PointToPoint => write!(f, "point-to-point"),
InterfaceKind::Bridge => write!(f, "bridge"),
InterfaceKind::Other(code) => write!(f, "other({code})"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AdminState {
Up,
Down,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DesiredAdminState {
Up,
Down,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InterfaceConfig {
interface_id: InterfaceId,
admin_state: Option<DesiredAdminState>,
mtu: Option<u32>,
}
impl InterfaceConfig {
pub fn new(
interface_id: InterfaceId,
admin_state: Option<DesiredAdminState>,
mtu: Option<u32>,
) -> Result<Self> {
if admin_state.is_none() && mtu.is_none() || mtu == Some(0) {
return Err(Error::InvalidState);
}
Ok(Self {
interface_id,
admin_state,
mtu,
})
}
pub const fn interface_id(&self) -> InterfaceId {
self.interface_id
}
pub const fn admin_state(&self) -> Option<DesiredAdminState> {
self.admin_state
}
pub const fn mtu(&self) -> Option<u32> {
self.mtu
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OperationalState {
Up,
Down,
NoCarrier,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Interface {
pub id: InterfaceId,
pub index: u32,
pub name: String,
pub kind: InterfaceKind,
pub mac: Option<MacAddress>,
pub mtu: Option<u32>,
pub admin_state: AdminState,
pub operational_state: OperationalState,
}
impl Interface {
pub fn new(id: InterfaceId, index: u32, name: impl Into<String>, kind: InterfaceKind) -> Self {
Self {
id,
index,
name: name.into(),
kind,
mac: None,
mtu: None,
admin_state: AdminState::Unknown,
operational_state: OperationalState::Unknown,
}
}
pub fn with_mac(mut self, mac: MacAddress) -> Self {
self.mac = Some(mac);
self
}
pub fn with_mtu(mut self, mtu: u32) -> Self {
self.mtu = Some(mtu);
self
}
pub fn with_admin_state(mut self, state: AdminState) -> Self {
self.admin_state = state;
self
}
pub fn with_operational_state(mut self, state: OperationalState) -> Self {
self.operational_state = state;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_interface_has_no_optional_fields() {
let iface = Interface::new(InterfaceId::new(1), 1, "eth0", InterfaceKind::Ethernet);
assert!(iface.mac.is_none());
assert!(iface.mtu.is_none());
assert_eq!(iface.admin_state, AdminState::Unknown);
assert_eq!(iface.operational_state, OperationalState::Unknown);
}
#[test]
fn builder_methods_set_optional_fields() {
let mac = MacAddress::new([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
let iface = Interface::new(InterfaceId::new(1), 1, "eth0", InterfaceKind::Ethernet)
.with_mac(mac)
.with_mtu(1500)
.with_admin_state(AdminState::Up)
.with_operational_state(OperationalState::Up);
assert_eq!(iface.mac, Some(mac));
assert_eq!(iface.mtu, Some(1500));
assert_eq!(iface.admin_state, AdminState::Up);
assert_eq!(iface.operational_state, OperationalState::Up);
}
#[test]
fn kind_displays() {
assert_eq!(InterfaceKind::Ethernet.to_string(), "ethernet");
assert_eq!(InterfaceKind::Wireless.to_string(), "wireless");
assert_eq!(InterfaceKind::Loopback.to_string(), "loopback");
assert_eq!(InterfaceKind::PointToPoint.to_string(), "point-to-point");
assert_eq!(InterfaceKind::Bridge.to_string(), "bridge");
assert_eq!(InterfaceKind::Other(42).to_string(), "other(42)");
}
#[test]
fn interface_config_preserves_only_requested_settings() {
let config =
InterfaceConfig::new(InterfaceId::new(7), Some(DesiredAdminState::Up), Some(1500))
.expect("valid partial patch");
assert_eq!(config.interface_id(), InterfaceId::new(7));
assert_eq!(config.admin_state(), Some(DesiredAdminState::Up));
assert_eq!(config.mtu(), Some(1500));
}
#[test]
fn interface_config_allows_each_setting_independently() {
let admin_only =
InterfaceConfig::new(InterfaceId::new(7), Some(DesiredAdminState::Down), None)
.expect("admin-only patch");
let mtu_only =
InterfaceConfig::new(InterfaceId::new(7), None, Some(9000)).expect("mtu-only patch");
assert_eq!(admin_only.mtu(), None);
assert_eq!(mtu_only.admin_state(), None);
}
#[test]
fn interface_config_rejects_empty_or_zero_mtu_patches() {
assert!(matches!(
InterfaceConfig::new(InterfaceId::new(7), None, None),
Err(Error::InvalidState)
));
assert!(matches!(
InterfaceConfig::new(InterfaceId::new(7), None, Some(0)),
Err(Error::InvalidState)
));
}
}