#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Route {
dst: u32,
next_hop: u32,
cost: u16,
}
impl Route {
pub fn dst(&self) -> u32 {
self.dst
}
pub fn next_hop(&self) -> u32 {
self.next_hop
}
pub fn cost(&self) -> u16 {
self.cost
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Forward {
Deliver,
Relay(u32),
Flood,
}
#[derive(Clone, Copy, Debug)]
pub struct Router<const N: usize> {
me: u32,
routes: [Option<Route>; N],
}
impl<const N: usize> Router<N> {
pub const fn new(me: u32) -> Self {
Router {
me,
routes: [None; N],
}
}
pub fn address(&self) -> u32 {
self.me
}
pub fn observe(&mut self, origin: u32, via: u32, cost: u16) -> bool {
observe_into(&mut self.routes, self.me, origin, via, cost)
}
pub fn next_hop(&self, dst: u32) -> Option<u32> {
self.route(dst).map(|route| route.next_hop)
}
pub fn cost(&self, dst: u32) -> Option<u16> {
self.route(dst).map(|route| route.cost)
}
pub fn route(&self, dst: u32) -> Option<Route> {
route_in(&self.routes, dst)
}
pub fn forward(&self, dst: u32) -> Forward {
forward_in(&self.routes, self.me, dst)
}
pub fn forget(&mut self, dst: u32) {
forget_in(&mut self.routes, dst)
}
pub fn len(&self) -> usize {
self.routes.iter().filter(|slot| slot.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.routes.iter().all(Option::is_none)
}
}
fn observe_into(routes: &mut [Option<Route>], me: u32, origin: u32, via: u32, cost: u16) -> bool {
if origin == me {
return false;
}
if let Some(index) = index_of_in(routes, origin) {
let route = routes[index]
.as_mut()
.expect("index_of_in points at a route");
if cost < route.cost || via == route.next_hop {
let changed = route.next_hop != via || route.cost != cost;
route.next_hop = via;
route.cost = cost;
return changed;
}
return false;
}
let new = Route {
dst: origin,
next_hop: via,
cost,
};
if let Some(empty) = routes.iter().position(Option::is_none) {
routes[empty] = Some(new);
return true;
}
if let Some((worst, worst_cost)) = routes
.iter()
.enumerate()
.filter_map(|(i, slot)| slot.as_ref().map(|route| (i, route.cost)))
.max_by_key(|&(_, cost)| cost)
{
if cost < worst_cost {
routes[worst] = Some(new);
return true;
}
}
false
}
fn index_of_in(routes: &[Option<Route>], dst: u32) -> Option<usize> {
routes
.iter()
.position(|slot| slot.as_ref().is_some_and(|route| route.dst == dst))
}
fn route_in(routes: &[Option<Route>], dst: u32) -> Option<Route> {
index_of_in(routes, dst).map(|index| routes[index].expect("index_of_in points at a route"))
}
fn forward_in(routes: &[Option<Route>], me: u32, dst: u32) -> Forward {
if dst == me {
return Forward::Deliver;
}
match route_in(routes, dst) {
Some(route) => Forward::Relay(route.next_hop),
None => Forward::Flood,
}
}
fn forget_in(routes: &mut [Option<Route>], dst: u32) {
if let Some(index) = index_of_in(routes, dst) {
routes[index] = None;
}
}
#[cfg(any(feature = "alloc", test))]
#[derive(Clone, Debug)]
pub struct DynamicRouter {
me: u32,
routes: alloc::vec::Vec<Option<Route>>,
}
#[cfg(any(feature = "alloc", test))]
impl DynamicRouter {
pub fn new(me: u32, capacity: usize) -> Self {
DynamicRouter {
me,
routes: alloc::vec![None; capacity],
}
}
pub fn address(&self) -> u32 {
self.me
}
pub fn capacity(&self) -> usize {
self.routes.len()
}
pub fn observe(&mut self, origin: u32, via: u32, cost: u16) -> bool {
observe_into(&mut self.routes, self.me, origin, via, cost)
}
pub fn next_hop(&self, dst: u32) -> Option<u32> {
route_in(&self.routes, dst).map(|route| route.next_hop)
}
pub fn cost(&self, dst: u32) -> Option<u16> {
route_in(&self.routes, dst).map(|route| route.cost)
}
pub fn route(&self, dst: u32) -> Option<Route> {
route_in(&self.routes, dst)
}
pub fn forward(&self, dst: u32) -> Forward {
forward_in(&self.routes, self.me, dst)
}
pub fn forget(&mut self, dst: u32) {
forget_in(&mut self.routes, dst)
}
pub fn len(&self) -> usize {
self.routes.iter().filter(|slot| slot.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.routes.iter().all(Option::is_none)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_learned_route_is_used() {
let mut router: Router<8> = Router::new(1);
assert!(router.observe(9, 5, 2));
assert_eq!(router.next_hop(9), Some(5));
assert_eq!(router.cost(9), Some(2));
assert_eq!(router.forward(9), Forward::Relay(5));
}
#[test]
fn a_packet_for_this_node_is_delivered() {
let router: Router<8> = Router::new(1);
assert_eq!(router.forward(1), Forward::Deliver);
}
#[test]
fn an_unknown_destination_floods() {
let router: Router<8> = Router::new(1);
assert_eq!(router.forward(42), Forward::Flood);
}
#[test]
fn a_cheaper_route_replaces_a_costlier_one() {
let mut router: Router<8> = Router::new(1);
router.observe(9, 5, 4);
assert!(router.observe(9, 7, 1));
assert_eq!(router.next_hop(9), Some(7));
assert_eq!(router.cost(9), Some(1));
}
#[test]
fn a_costlier_route_is_ignored() {
let mut router: Router<8> = Router::new(1);
router.observe(9, 7, 1);
assert!(!router.observe(9, 5, 4));
assert_eq!(router.next_hop(9), Some(7));
}
#[test]
fn the_current_next_hop_can_refresh_its_cost() {
let mut router: Router<8> = Router::new(1);
router.observe(9, 7, 1);
assert!(router.observe(9, 7, 3));
assert_eq!(router.cost(9), Some(3));
}
#[test]
fn we_never_route_to_ourselves() {
let mut router: Router<8> = Router::new(1);
assert!(!router.observe(1, 5, 1));
assert_eq!(router.route(1), None);
}
#[test]
fn a_full_table_evicts_its_costliest_route_for_a_cheaper_one() {
let mut router: Router<2> = Router::new(1);
router.observe(10, 2, 5);
router.observe(11, 3, 8); assert_eq!(router.len(), 2);
assert!(router.observe(12, 4, 2));
assert_eq!(router.next_hop(11), None); assert_eq!(router.next_hop(10), Some(2)); assert_eq!(router.next_hop(12), Some(4)); }
#[test]
fn a_full_table_keeps_its_routes_against_a_costlier_one() {
let mut router: Router<2> = Router::new(1);
router.observe(10, 2, 5);
router.observe(11, 3, 8);
assert!(!router.observe(12, 4, 9));
assert_eq!(router.next_hop(12), None);
assert_eq!(router.len(), 2);
}
#[test]
fn forgetting_a_route_drops_it() {
let mut router: Router<8> = Router::new(1);
router.observe(9, 5, 2);
router.forget(9);
assert_eq!(router.route(9), None);
assert!(router.is_empty());
}
#[test]
fn an_empty_router_reports_empty() {
let router: Router<8> = Router::new(1);
assert!(router.is_empty());
assert_eq!(router.len(), 0);
}
#[test]
fn a_zero_capacity_router_never_learns_but_does_not_panic() {
let mut router: Router<0> = Router::new(1);
assert!(!router.observe(9, 5, 2));
assert_eq!(router.next_hop(9), None);
assert_eq!(router.forward(9), Forward::Flood);
assert!(router.is_empty());
}
#[test]
fn a_runtime_sized_table_decides_the_same_way() {
let mut fixed: Router<8> = Router::new(1);
let mut dynamic = DynamicRouter::new(1, 8);
for (origin, via, cost) in [(9u32, 5u32, 4u16), (9, 7, 1), (10, 5, 3), (1, 2, 1)] {
assert_eq!(
fixed.observe(origin, via, cost),
dynamic.observe(origin, via, cost),
"the two tables learn identically"
);
}
for dst in [1u32, 9, 10, 42] {
assert_eq!(fixed.forward(dst), dynamic.forward(dst));
assert_eq!(fixed.route(dst), dynamic.route(dst));
}
assert_eq!(fixed.len(), dynamic.len());
fixed.forget(9);
dynamic.forget(9);
assert_eq!(fixed.forward(9), dynamic.forward(9));
assert_eq!(fixed.len(), dynamic.len());
}
#[test]
fn a_runtime_sized_table_fills_to_the_size_it_was_given() {
let mut router = DynamicRouter::new(1, 3);
assert_eq!(router.capacity(), 3);
assert!(router.is_empty());
for node in 0..10u32 {
router.observe(node + 0x100, 0x05, 4);
}
assert_eq!(router.len(), 3, "it holds no more than it was sized for");
}
#[test]
fn a_table_with_no_room_floods_everything() {
let mut router = DynamicRouter::new(1, 0);
assert!(!router.observe(9, 5, 2), "there is nowhere to put a route");
assert_eq!(router.forward(9), Forward::Flood);
assert_eq!(
router.forward(1),
Forward::Deliver,
"a local packet still arrives"
);
}
}