use alloc::collections::{BTreeMap, VecDeque};
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::core::error::{Error, Result};
use crate::core::sync::{AtomicU64, LockRank, Mutex, Ordering};
pub const MAX_FRAME_LEN: u64 = 1518;
pub const MIN_FRAME_LEN: u64 = 60;
pub const PORT_CAPACITY: usize = 256;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct MacAddr(pub [u8; 6]);
impl MacAddr {
pub const ZERO: MacAddr = MacAddr([0; 6]);
pub const BROADCAST: MacAddr = MacAddr([0xff; 6]);
#[must_use]
pub const fn new(octets: [u8; 6]) -> MacAddr {
MacAddr(octets)
}
#[must_use]
pub const fn octets(self) -> [u8; 6] {
self.0
}
#[must_use]
pub fn is_broadcast(self) -> bool {
self.0 == [0xff; 6]
}
#[must_use]
pub const fn is_multicast(self) -> bool {
self.0[0] & 0x01 != 0
}
#[must_use]
pub const fn is_unicast(self) -> bool {
!self.is_multicast()
}
#[must_use]
pub fn multicast_hash(self) -> u8 {
let mut crc: u32 = 0xffff_ffff;
for byte in self.0 {
crc ^= u32::from(byte);
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ 0xedb8_8320
} else {
crc >> 1
};
}
}
let low = (crc & 0x3f) as u8;
let mut index = 0u8;
for bit in 0..6 {
if low & (1 << bit) != 0 {
index |= 1 << (5 - bit);
}
}
index
}
pub fn parse(text: &str) -> Result<MacAddr> {
let bad = || Error::Config {
at: String::from("mac"),
message: alloc::format!(
"`{text}` is not a MAC address: want six hex octets, aa:bb:cc:dd:ee:ff"
),
};
let mut octets = [0u8; 6];
let mut parts = text.split([':', '-']);
for slot in &mut octets {
let part = parts.next().ok_or_else(bad)?;
if part.len() != 2 {
return Err(bad());
}
*slot = u8::from_str_radix(part, 16).map_err(|_| bad())?;
}
if parts.next().is_some() {
return Err(bad());
}
Ok(MacAddr(octets))
}
}
impl fmt::Display for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [a, b, c, d, e, g] = self.0;
write!(f, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}:{g:02x}")
}
}
impl fmt::Debug for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl From<[u8; 6]> for MacAddr {
fn from(octets: [u8; 6]) -> MacAddr {
MacAddr(octets)
}
}
pub trait NetLink: Send + Sync + fmt::Debug {
fn transmit(&self, now: u64, frame: &[u8]);
fn receive(&self, now: u64) -> Option<Vec<u8>>;
fn next_arrival(&self) -> Option<u64>;
fn link_up(&self) -> bool {
true
}
fn set_mac(&self, mac: MacAddr) {
let _ = mac;
}
}
pub struct NetPort {
state: Mutex<PortState>,
earliest: AtomicU64,
}
#[derive(Debug)]
struct PortState {
inbound: BTreeMap<(u64, u64), Vec<u8>>,
seq: u64,
outbound: VecDeque<Vec<u8>>,
link_up: bool,
mac: MacAddr,
loopback: Option<u64>,
dropped_in: u64,
dropped_out: u64,
}
impl Default for PortState {
fn default() -> PortState {
PortState {
inbound: BTreeMap::new(),
seq: 0,
outbound: VecDeque::new(),
link_up: true,
mac: MacAddr::ZERO,
loopback: None,
dropped_in: 0,
dropped_out: 0,
}
}
}
impl fmt::Debug for NetPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.state.try_lock() {
Some(state) => f
.debug_struct("NetPort")
.field("mac", &state.mac)
.field("link_up", &state.link_up)
.field("inbound", &state.inbound.len())
.field("outbound", &state.outbound.len())
.finish(),
None => f
.debug_struct("NetPort")
.field("state", &"<in use>")
.finish(),
}
}
}
impl Default for NetPort {
fn default() -> NetPort {
NetPort::new()
}
}
impl NetPort {
#[must_use]
pub fn new() -> NetPort {
NetPort {
state: Mutex::with_rank(LockRank::LEAF, PortState::default()),
earliest: AtomicU64::new(u64::MAX),
}
}
fn publish(&self, state: &PortState) {
self.earliest.store(
state.inbound.keys().next().map_or(u64::MAX, |k| k.0),
Ordering::Relaxed,
);
}
#[must_use]
pub fn loopback(latency: u64) -> NetPort {
let port = NetPort::new();
port.state.lock().loopback = Some(latency);
port
}
pub fn deliver_at(&self, tick: u64, frame: &[u8]) -> bool {
let mut state = self.state.lock();
if state.inbound.len() >= PORT_CAPACITY {
state.dropped_in += 1;
return false;
}
let seq = state.seq;
state.seq += 1;
state.inbound.insert((tick, seq), frame.to_vec());
self.publish(&state);
true
}
pub fn deliver(&self, frame: &[u8]) -> bool {
self.deliver_at(0, frame)
}
#[must_use]
pub fn take(&self) -> Option<Vec<u8>> {
self.state.lock().outbound.pop_front()
}
#[must_use]
pub fn drain(&self) -> Vec<Vec<u8>> {
self.state.lock().outbound.drain(..).collect()
}
#[must_use]
pub fn pending_output(&self) -> usize {
self.state.lock().outbound.len()
}
#[must_use]
pub fn pending_input(&self) -> usize {
self.state.lock().inbound.len()
}
#[must_use]
pub fn dropped(&self) -> (u64, u64) {
let state = self.state.lock();
(state.dropped_in, state.dropped_out)
}
pub fn set_link(&self, up: bool) {
self.state.lock().link_up = up;
}
#[must_use]
pub fn mac(&self) -> MacAddr {
self.state.lock().mac
}
pub fn drop_queued(&self) {
let mut state = self.state.lock();
state.inbound.clear();
self.publish(&state);
}
pub fn clear(&self) {
let mut state = self.state.lock();
state.inbound.clear();
state.outbound.clear();
self.publish(&state);
}
}
impl NetLink for NetPort {
fn transmit(&self, now: u64, frame: &[u8]) {
let mut state = self.state.lock();
if let Some(latency) = state.loopback {
if state.inbound.len() < PORT_CAPACITY {
let seq = state.seq;
state.seq += 1;
state
.inbound
.insert((now.saturating_add(latency), seq), frame.to_vec());
self.publish(&state);
} else {
state.dropped_in += 1;
}
}
if state.outbound.len() >= PORT_CAPACITY {
state.dropped_out += 1;
return;
}
state.outbound.push_back(frame.to_vec());
}
fn receive(&self, now: u64) -> Option<Vec<u8>> {
let mut state = self.state.lock();
let key = *state.inbound.keys().next()?;
if key.0 > now {
return None;
}
let frame = state.inbound.remove(&key)?;
self.publish(&state);
Some(frame)
}
fn next_arrival(&self) -> Option<u64> {
match self.earliest.load(Ordering::Relaxed) {
u64::MAX => None,
at => Some(at),
}
}
fn link_up(&self) -> bool {
self.state.lock().link_up
}
fn set_mac(&self, mac: MacAddr) {
self.state.lock().mac = mac;
}
}
pub mod ports {
use super::NetPort;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::error::Result;
use crate::core::hosts::{HostKind, HostObjects};
use crate::core::props::Props;
use crate::core::record::{Channel, FnSink, InputSink};
pub const KIND: HostKind = HostKind::new("netdev");
pub fn open(hosts: &HostObjects, name: &str) -> Result<Arc<NetPort>> {
hosts.open(KIND, name, NetPort::new)
}
pub fn attach(props: &Props, name: &str) -> Result<Arc<NetPort>> {
props.host(KIND, name, NetPort::new)
}
pub fn get(hosts: &HostObjects, name: &str) -> Result<Option<Arc<NetPort>>> {
hosts.get(KIND, name)
}
pub fn close(hosts: &HostObjects, name: &str) -> bool {
hosts.close(KIND, name)
}
#[must_use]
pub fn names(hosts: &HostObjects) -> Vec<String> {
hosts.names(KIND)
}
#[must_use]
pub fn channel(name: &str) -> Channel {
Channel::new(KIND, name)
}
#[must_use]
pub fn sink(port: &Arc<NetPort>) -> Arc<dyn InputSink> {
let receiving = Arc::clone(port);
let rewinding = Arc::clone(port);
Arc::new(
FnSink::new("netdev", move |frame: &[u8]| {
receiving.deliver(frame);
})
.on_rewind(move || rewinding.drop_queued()),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec;
#[test]
fn a_mac_round_trips_through_its_text_form() {
let mac = MacAddr::parse("52:54:00:12:34:56").expect("six octets");
assert_eq!(mac.octets(), [0x52, 0x54, 0x00, 0x12, 0x34, 0x56]);
assert_eq!(mac.to_string(), "52:54:00:12:34:56");
assert_eq!(MacAddr::parse("52-54-00-12-34-56").unwrap(), mac);
assert!(mac.is_unicast());
assert!(MacAddr::BROADCAST.is_broadcast());
assert!(MacAddr::BROADCAST.is_multicast());
}
#[test]
fn a_mac_that_is_not_six_octets_is_rejected() {
for bad in [
"",
"52:54:00:12:34",
"52:54:00:12:34:56:78",
"zz:54:00:12:34:56",
"5:54:00:12:34:56",
] {
assert!(MacAddr::parse(bad).is_err(), "`{bad}` should not parse");
}
}
#[test]
fn frames_cross_in_both_directions_without_meeting() {
let port = NetPort::new();
assert!(port.deliver(b"inbound"));
port.transmit(0, b"outbound");
assert_eq!(port.receive(0).as_deref(), Some(&b"inbound"[..]));
assert_eq!(port.receive(0), None);
assert_eq!(port.drain(), vec![b"outbound".to_vec()]);
}
#[test]
fn a_frame_from_the_future_is_not_visible_yet() {
let port = NetPort::new();
port.deliver_at(100, b"later");
assert_eq!(port.next_arrival(), Some(100));
assert_eq!(port.receive(99), None, "99 is before 100");
assert_eq!(port.receive(100).as_deref(), Some(&b"later"[..]));
assert_eq!(port.next_arrival(), None);
}
#[test]
fn two_frames_at_one_tick_come_out_in_the_order_they_went_in() {
let port = NetPort::new();
for i in 0..8u8 {
port.deliver_at(7, &[i]);
}
for i in 0..8u8 {
assert_eq!(port.receive(7).as_deref(), Some(&[i][..]));
}
}
#[test]
fn a_loopback_port_hands_a_transmission_back_after_its_latency() {
let port = NetPort::loopback(10);
port.transmit(100, b"hello");
assert_eq!(port.next_arrival(), Some(110));
assert_eq!(port.receive(109), None);
assert_eq!(port.receive(110).as_deref(), Some(&b"hello"[..]));
assert_eq!(port.drain(), vec![b"hello".to_vec()]);
}
#[test]
fn a_frame_reaches_the_port_through_the_record_seam() {
use crate::core::clock::GlobalTime;
use crate::core::record::Recorder;
let hosts = crate::core::hosts::HostObjects::new();
let port = ports::open(&hosts, "net0").unwrap();
let recorder = Recorder::recording();
recorder
.register(ports::channel("net0"), ports::sink(&port))
.unwrap();
recorder.post(&ports::channel("net0"), b"frame").unwrap();
assert_eq!(port.pending_input(), 0, "posting delivers nothing");
recorder.deliver(GlobalTime::from_nanos(1_000)).unwrap();
assert_eq!(port.receive(0).as_deref(), Some(&b"frame"[..]));
let log = recorder.log();
assert_eq!(log.len(), 1);
assert_eq!(log.events()[0].channel.to_string(), "netdev:net0");
assert_eq!(log.events()[0].payload, b"frame");
}
#[test]
fn a_rewind_drops_what_the_guest_has_not_taken() {
let port = NetPort::new();
port.deliver(b"queued");
port.transmit(0, b"sent");
port.drop_queued();
assert_eq!(port.pending_input(), 0);
assert_eq!(port.next_arrival(), None);
assert_eq!(
port.pending_output(),
1,
"what the guest already transmitted has left and is not rewound"
);
}
#[test]
fn a_full_port_drops_rather_than_growing() {
let port = NetPort::new();
for i in 0..PORT_CAPACITY {
assert!(port.deliver_at(i as u64, b"x"));
}
assert!(!port.deliver(b"one too many"));
assert_eq!(port.dropped().0, 1);
for _ in 0..PORT_CAPACITY + 4 {
port.transmit(0, b"y");
}
assert_eq!(port.pending_output(), PORT_CAPACITY);
assert_eq!(port.dropped().1, 4);
}
#[test]
fn the_carrier_and_the_address_are_part_of_the_seam() {
let port = NetPort::new();
assert!(port.link_up());
port.set_link(false);
assert!(!port.link_up());
assert_eq!(port.mac(), MacAddr::ZERO);
port.set_mac(MacAddr::parse("02:00:00:00:00:01").unwrap());
assert_eq!(port.mac().to_string(), "02:00:00:00:00:01");
}
#[test]
fn a_name_reaches_the_same_port_from_both_ends() {
let hosts = crate::core::hosts::HostObjects::new();
let device_end: Arc<dyn NetLink> = ports::open(&hosts, "net0").unwrap();
let host_end = ports::open(&hosts, "net0").unwrap();
host_end.deliver(b"frame");
assert_eq!(device_end.receive(0).as_deref(), Some(&b"frame"[..]));
device_end.transmit(0, b"reply");
assert_eq!(host_end.drain(), vec![b"reply".to_vec()]);
assert_eq!(ports::names(&hosts), ["net0"]);
assert!(ports::close(&hosts, "net0"));
assert!(ports::get(&hosts, "net0").unwrap().is_none());
}
#[test]
fn two_builds_with_one_port_name_are_two_ports() {
let left = crate::core::hosts::HostObjects::new();
let right = crate::core::hosts::HostObjects::new();
let a = ports::open(&left, "net0").unwrap();
let b = ports::open(&right, "net0").unwrap();
assert!(!Arc::ptr_eq(&a, &b));
a.deliver(b"only mine");
assert_eq!(b.pending_input(), 0);
}
#[test]
fn the_multicast_hash_is_a_function_of_the_address() {
for mac in [
MacAddr::BROADCAST,
MacAddr::parse("01:00:5e:00:00:01").unwrap(),
MacAddr::parse("33:33:00:00:00:01").unwrap(),
] {
assert!(mac.multicast_hash() < 64);
}
assert_ne!(
MacAddr::parse("01:00:5e:00:00:01")
.unwrap()
.multicast_hash(),
MacAddr::parse("01:00:5e:00:00:02")
.unwrap()
.multicast_hash(),
);
}
}