use ipnetx::{interfaces::IpAddress, prefix::IpPrefix};
use std::fmt;
use std::marker::PhantomData;
mod node;
use node::TbNode;
const STRIDE: u32 = 4;
fn nibble(addr: u128, addr_bits: u32, hop: u32) -> u32 {
let shift = addr_bits - (hop + 1) * STRIDE;
((addr >> shift) & 0xF) as u32
}
fn rank(bitmap: u32, position: u32) -> usize {
(bitmap & ((1 << position) - 1)).count_ones() as usize
}
#[derive(Clone)]
pub struct RouteMap<A: IpAddress, V> {
root: TbNode<V>,
count: usize,
_marker: PhantomData<A>,
}
impl<A: IpAddress, V> RouteMap<A, V> {
pub fn new() -> Self {
Self {
root: TbNode::new(),
count: 0,
_marker: PhantomData,
}
}
pub fn insert(&mut self, prefix: IpPrefix<A>, value: V) {
let prefix = prefix.masked();
let addr = prefix.ip().to_u128();
let len = prefix.mask() as u32;
let is_new = insert_at(
&mut self.root,
addr,
A::BITS as u32,
0,
len / STRIDE,
len % STRIDE,
value,
);
if is_new {
self.count += 1;
}
}
pub fn longest_match(&self, addr: A) -> Option<&V> {
self.longest_match_impl(addr.to_u128()).map(|(v, _)| v)
}
pub fn longest_match_entry(&self, addr: A) -> Option<(IpPrefix<A>, &V)> {
let addr = addr.to_u128();
self.longest_match_impl(addr).map(|(value, best_len)| {
let prefix = IpPrefix::new(A::from_u128(addr), best_len as u8)
.expect("best_len never exceeds A::BITS")
.masked();
(prefix, value)
})
}
fn longest_match_impl(&self, addr: u128) -> Option<(&V, u32)> {
let addr_bits = A::BITS as u32;
let total_strides = addr_bits / STRIDE;
let mut node = &self.root;
let mut best: Option<(&V, u32)> = None;
let mut last_advanced = false;
for hop in 0..total_strides {
let nib = nibble(addr, addr_bits, hop);
for rel_len in 0..STRIDE {
let rel_v = if rel_len == 0 {
0u32
} else {
nib >> (STRIDE - rel_len)
};
let bpos = (1u32 << rel_len) + rel_v;
if (node.internal >> bpos) & 1 == 1 {
best = Some((&node.values[rank(node.internal, bpos)], hop * STRIDE + rel_len));
}
}
if (node.external >> nib) & 1 == 0 {
last_advanced = false;
break;
}
node = &node.children[rank(node.external, nib)];
last_advanced = true;
}
if last_advanced && (node.internal >> 1) & 1 == 1 {
best = Some((&node.values[rank(node.internal, 1)], addr_bits));
}
best
}
pub fn remove(&mut self, prefix: IpPrefix<A>) -> Option<V> {
let prefix = prefix.masked();
let addr = prefix.ip().to_u128();
let len = prefix.mask() as u32;
let (value, _) = remove_at(
&mut self.root,
addr,
A::BITS as u32,
0,
len / STRIDE,
len % STRIDE,
);
if value.is_some() {
self.count -= 1;
}
value
}
pub fn get(&self, prefix: IpPrefix<A>) -> Option<&V> {
let prefix = prefix.masked();
let addr = prefix.ip().to_u128();
let len = prefix.mask() as u32;
get_at(
&self.root,
addr,
A::BITS as u32,
0,
len / STRIDE,
len % STRIDE,
)
}
pub fn iter(&self) -> Iter<'_, A, V> {
Iter {
stack: vec![IterFrame {
node: &self.root,
hop: 0,
addr: 0,
internal_cursor: 1,
external_cursor: 0,
}],
addr_bits: A::BITS as u32,
_marker: PhantomData,
}
}
pub fn contains(&self, prefix: IpPrefix<A>) -> bool {
let prefix = prefix.masked();
let addr = prefix.ip().to_u128();
let len = prefix.mask() as u32;
contains_at(
&self.root,
addr,
A::BITS as u32,
0,
len / STRIDE,
len % STRIDE,
)
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn clear(&mut self) {
self.root.internal = 0;
self.root.external = 0;
self.count = 0;
self.root.children.clear();
self.root.values.clear();
}
}
impl<A: IpAddress, V> Default for RouteMap<A, V> {
fn default() -> Self {
Self::new()
}
}
impl<A: IpAddress + fmt::Display, V: fmt::Debug> fmt::Debug for RouteMap<A, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut map = f.debug_map();
for (prefix, value) in self.iter() {
let key = format!("{}/{}", prefix.ip(), prefix.mask());
map.entry(&key, value);
}
map.finish()
}
}
impl<A: IpAddress, V> FromIterator<(IpPrefix<A>, V)> for RouteMap<A, V> {
fn from_iter<I: IntoIterator<Item = (IpPrefix<A>, V)>>(iter: I) -> Self {
let mut table = Self::new();
for (prefix, value) in iter {
table.insert(prefix, value);
}
table
}
}
impl<'a, A: IpAddress, V> IntoIterator for &'a RouteMap<A, V> {
type Item = (IpPrefix<A>, &'a V);
type IntoIter = Iter<'a, A, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
fn insert_at<V>(
node: &mut TbNode<V>,
addr: u128,
addr_bits: u32,
current_hop: u32,
full_hops: u32,
rel_len: u32,
value: V,
) -> bool {
if current_hop == full_hops {
let nib = if rel_len > 0 {
nibble(addr, addr_bits, current_hop) >> (STRIDE - rel_len)
} else {
0
};
let bpos = (1u32 << rel_len) + nib;
let idx = rank(node.internal, bpos);
if (node.internal >> bpos) & 1 == 1 {
node.values[idx] = value;
false } else {
node.values.insert(idx, value);
node.internal |= 1 << bpos;
true }
} else {
let nib = nibble(addr, addr_bits, current_hop);
let already_exists = (node.external >> nib) & 1 != 0;
let child_idx = rank(node.external, nib);
if !already_exists {
node.children.insert(child_idx, TbNode::new());
node.external |= 1 << nib;
}
insert_at(
&mut node.children[child_idx],
addr,
addr_bits,
current_hop + 1,
full_hops,
rel_len,
value,
)
}
}
fn get_at<V>(
node: &TbNode<V>,
addr: u128,
addr_bits: u32,
current_hop: u32,
full_hops: u32,
rel_len: u32,
) -> Option<&V> {
if current_hop == full_hops {
let nib = if rel_len > 0 {
nibble(addr, addr_bits, current_hop) >> (STRIDE - rel_len)
} else {
0
};
let bpos = (1u32 << rel_len) + nib;
if (node.internal >> bpos) & 1 == 1 {
Some(&node.values[rank(node.internal, bpos)])
} else {
None
}
} else {
let nib = nibble(addr, addr_bits, current_hop);
if (node.external >> nib) & 1 == 0 {
return None;
}
let child_idx = rank(node.external, nib);
get_at(
&node.children[child_idx],
addr,
addr_bits,
current_hop + 1,
full_hops,
rel_len,
)
}
}
fn remove_at<V>(
node: &mut TbNode<V>,
addr: u128,
addr_bits: u32,
current_hop: u32,
full_hops: u32,
rel_len: u32,
) -> (Option<V>, bool) {
if current_hop == full_hops {
let nib = if rel_len > 0 {
nibble(addr, addr_bits, current_hop) >> (STRIDE - rel_len)
} else {
0
};
let bpos = (1u32 << rel_len) + nib;
if (node.internal >> bpos) & 1 == 0 {
return (None, false);
}
let idx = rank(node.internal, bpos);
let value = node.values.remove(idx);
node.internal &= !(1u32 << bpos);
let is_empty = node.internal == 0 && node.external == 0;
(Some(value), is_empty)
} else {
let nib = nibble(addr, addr_bits, current_hop);
if (node.external >> nib) & 1 == 0 {
return (None, false);
}
let child_idx = rank(node.external, nib);
let (value, prune) = remove_at(
&mut node.children[child_idx],
addr,
addr_bits,
current_hop + 1,
full_hops,
rel_len,
);
if prune {
node.children.remove(child_idx);
node.external &= !(1u32 << nib);
}
let is_empty = node.internal == 0 && node.external == 0;
(value, is_empty)
}
}
fn contains_at<V>(
node: &TbNode<V>,
addr: u128,
addr_bits: u32,
current_hop: u32,
full_hops: u32,
rel_len: u32,
) -> bool {
if current_hop == full_hops {
let nib = if rel_len > 0 {
nibble(addr, addr_bits, current_hop) >> (STRIDE - rel_len)
} else {
0
};
let bpos = (1u32 << rel_len) + nib;
(node.internal >> bpos) & 1 == 1
} else {
let nib = nibble(addr, addr_bits, current_hop);
if (node.external >> nib) & 1 == 0 {
return false;
}
let child_idx = rank(node.external, nib);
contains_at(
&node.children[child_idx],
addr,
addr_bits,
current_hop + 1,
full_hops,
rel_len,
)
}
}
struct IterFrame<'a, V> {
node: &'a TbNode<V>,
hop: u32,
addr: u128,
internal_cursor: u32,
external_cursor: u32,
}
pub struct Iter<'a, A: IpAddress, V> {
stack: Vec<IterFrame<'a, V>>,
addr_bits: u32,
_marker: PhantomData<A>,
}
impl<'a, A: IpAddress, V> Iterator for Iter<'a, A, V> {
type Item = (IpPrefix<A>, &'a V);
fn next(&mut self) -> Option<Self::Item> {
loop {
let depth = self.stack.len();
if depth == 0 {
return None;
}
let node: &'a TbNode<V> = self.stack[depth - 1].node;
let hop = self.stack[depth - 1].hop;
let addr = self.stack[depth - 1].addr;
let ic = self.stack[depth - 1].internal_cursor;
if ic <= 15 {
let above = node.internal >> ic;
if above != 0 {
let bpos = ic + above.trailing_zeros();
self.stack[depth - 1].internal_cursor = bpos + 1;
let rel_len = 31 - bpos.leading_zeros(); let rel_bits = bpos - (1u32 << rel_len);
let full_len = hop * STRIDE + rel_len;
let full_addr = if rel_len > 0 {
addr | ((rel_bits as u128) << (self.addr_bits - hop * STRIDE - rel_len))
} else {
addr
};
let idx = rank(node.internal, bpos);
let value: &'a V = &node.values[idx];
let prefix = IpPrefix::new(A::from_u128(full_addr), full_len as u8).unwrap();
return Some((prefix, value));
}
self.stack[depth - 1].internal_cursor = 16;
}
let ec = self.stack[depth - 1].external_cursor;
let pushed = ec <= 15 && {
let above = node.external >> ec;
if above == 0 {
self.stack[depth - 1].external_cursor = 16;
false
} else {
let nib = ec + above.trailing_zeros();
self.stack[depth - 1].external_cursor = nib + 1;
let child_addr =
addr | ((nib as u128) << (self.addr_bits - (hop + 1) * STRIDE));
let child_idx = rank(node.external, nib);
let child_node: &'a TbNode<V> = &node.children[child_idx];
self.stack.push(IterFrame {
node: child_node,
hop: hop + 1,
addr: child_addr,
internal_cursor: 1,
external_cursor: 0,
});
true
}
};
if !pushed {
self.stack.pop();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn rank_empty_bitmap() {
assert_eq!(rank(0b000000, 3), 0);
}
#[test]
fn rank_all_set_below() {
assert_eq!(rank(0b00000111, 3), 3);
}
#[test]
fn rank_bit_at_position_does_not_count() {
assert_eq!(rank(0b00001000, 3), 0);
}
#[test]
fn rank_mixed() {
assert_eq!(rank(0b00001010, 6), 2);
}
#[test]
fn rank_example_from_reference() {
let bitmap = (1 << 1) | (1 << 3) | (1 << 6);
assert_eq!(rank(bitmap, 6), 2);
}
#[test]
fn empty_table_returns_none() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert_eq!(table.longest_match("10.0.0.1".parse().unwrap()), None);
}
#[test]
fn default_route_matches_any_address() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
assert_eq!(
table.longest_match("1.2.3.4".parse().unwrap()),
Some(&"default")
);
assert_eq!(
table.longest_match("255.255.255.255".parse().unwrap()),
Some(&"default")
);
assert_eq!(
table.longest_match("0.0.0.0".parse().unwrap()),
Some(&"default")
);
}
#[test]
fn default_route_is_fallback_when_no_specific_match() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"ten")
);
assert_eq!(
table.longest_match("192.168.1.1".parse().unwrap()),
Some(&"default")
);
}
#[test]
fn single_prefix_hit() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"ten")
);
}
#[test]
fn single_prefix_miss() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.longest_match("11.0.0.1".parse().unwrap()), None);
}
#[test]
fn network_address_itself_matches() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(
table.longest_match("10.0.0.0".parse().unwrap()),
Some(&"ten")
);
}
#[test]
fn address_just_outside_prefix_misses() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.longest_match("11.0.0.0".parse().unwrap()), None);
}
#[test]
fn last_address_in_prefix_matches() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/24".parse().unwrap(), "subnet");
assert_eq!(
table.longest_match("10.0.0.255".parse().unwrap()),
Some(&"subnet")
);
}
#[test]
fn most_specific_prefix_wins() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
assert_eq!(
table.longest_match("10.20.5.1".parse().unwrap()),
Some(&"specific")
);
assert_eq!(
table.longest_match("10.99.0.1".parse().unwrap()),
Some(&"broad")
);
}
#[test]
fn three_levels_of_nesting() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "level-1");
table.insert("10.20.0.0/16".parse().unwrap(), "level-2");
table.insert("10.20.30.0/24".parse().unwrap(), "level-3");
assert_eq!(
table.longest_match("10.20.30.1".parse().unwrap()),
Some(&"level-3")
);
assert_eq!(
table.longest_match("10.20.99.1".parse().unwrap()),
Some(&"level-2")
);
assert_eq!(
table.longest_match("10.99.0.1".parse().unwrap()),
Some(&"level-1")
);
assert_eq!(table.longest_match("9.0.0.1".parse().unwrap()), None);
}
#[test]
fn slash32_matches_only_that_host() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.0.0.1/32".parse().unwrap(), "host");
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"host")
);
assert_eq!(
table.longest_match("10.0.0.2".parse().unwrap()),
Some(&"broad")
);
}
#[test]
fn inserting_same_prefix_twice_overwrites_value() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "first");
table.insert("10.0.0.0/8".parse().unwrap(), "second");
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"second")
);
}
#[test]
fn non_overlapping_prefixes_do_not_cross_match() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.insert("192.168.0.0/16".parse().unwrap(), "office");
assert_eq!(
table.longest_match("10.1.2.3".parse().unwrap()),
Some(&"ten")
);
assert_eq!(
table.longest_match("192.168.1.1".parse().unwrap()),
Some(&"office")
);
assert_eq!(table.longest_match("172.16.0.1".parse().unwrap()), None);
}
#[test]
fn ipv6_basic_match() {
let mut table: RouteMap<Ipv6Addr, &str> = RouteMap::new();
table.insert("2001:db8::/32".parse().unwrap(), "docs");
assert_eq!(
table.longest_match("2001:db8::1".parse().unwrap()),
Some(&"docs")
);
assert_eq!(table.longest_match("2001:db9::1".parse().unwrap()), None);
}
#[test]
fn ipv6_most_specific_wins() {
let mut table: RouteMap<Ipv6Addr, &str> = RouteMap::new();
table.insert("2001:db8::/32".parse().unwrap(), "broad");
table.insert("2001:db8:1::/48".parse().unwrap(), "specific");
assert_eq!(
table.longest_match("2001:db8:1::1".parse().unwrap()),
Some(&"specific")
);
assert_eq!(
table.longest_match("2001:db8:2::1".parse().unwrap()),
Some(&"broad")
);
}
#[test]
fn ipv6_default_route() {
let mut table: RouteMap<Ipv6Addr, &str> = RouteMap::new();
table.insert("::/0".parse().unwrap(), "default");
assert_eq!(
table.longest_match("2001:db8::1".parse().unwrap()),
Some(&"default")
);
assert_eq!(
table.longest_match("::1".parse().unwrap()),
Some(&"default")
);
}
#[test]
fn entry_empty_table_returns_none() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert_eq!(table.longest_match_entry("10.0.0.1".parse().unwrap()), None);
}
#[test]
fn entry_returns_most_specific_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
assert_eq!(
table.longest_match_entry("10.20.5.1".parse().unwrap()),
Some(("10.20.0.0/16".parse().unwrap(), &"specific")),
);
assert_eq!(
table.longest_match_entry("10.99.0.1".parse().unwrap()),
Some(("10.0.0.0/8".parse().unwrap(), &"broad")),
);
}
#[test]
fn entry_default_route_yields_slash0() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
assert_eq!(
table.longest_match_entry("192.168.1.1".parse().unwrap()),
Some(("0.0.0.0/0".parse().unwrap(), &"default")),
);
}
#[test]
fn entry_slash32_host_yields_full_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.0.0.1/32".parse().unwrap(), "host");
assert_eq!(
table.longest_match_entry("10.0.0.1".parse().unwrap()),
Some(("10.0.0.1/32".parse().unwrap(), &"host")),
);
assert_eq!(
table.longest_match_entry("10.0.0.2".parse().unwrap()),
Some(("10.0.0.0/8".parse().unwrap(), &"broad")),
);
}
#[test]
fn entry_miss_returns_none() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.longest_match_entry("11.0.0.1".parse().unwrap()), None);
}
#[test]
fn entry_returned_prefix_is_canonical() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
let (prefix, value) = table
.longest_match_entry("10.20.5.1".parse().unwrap())
.expect("address is covered");
assert_eq!(table.get(prefix), Some(value));
}
#[test]
fn entry_ipv6_most_specific_wins() {
let mut table: RouteMap<Ipv6Addr, &str> = RouteMap::new();
table.insert("2001:db8::/32".parse().unwrap(), "broad");
table.insert("2001:db8:1::/48".parse().unwrap(), "specific");
assert_eq!(
table.longest_match_entry("2001:db8:1::1".parse().unwrap()),
Some(("2001:db8:1::/48".parse().unwrap(), &"specific")),
);
assert_eq!(
table.longest_match_entry("2001:db8:2::1".parse().unwrap()),
Some(("2001:db8::/32".parse().unwrap(), &"broad")),
);
}
#[test]
fn entry_ipv6_slash128_host_yields_full_prefix() {
let mut table: RouteMap<Ipv6Addr, &str> = RouteMap::new();
table.insert("::/0".parse().unwrap(), "default");
table.insert("2001:db8::1/128".parse().unwrap(), "host");
assert_eq!(
table.longest_match_entry("2001:db8::1".parse().unwrap()),
Some(("2001:db8::1/128".parse().unwrap(), &"host")),
);
}
#[test]
fn entry_agrees_with_longest_match_value() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
for addr in ["10.20.5.1", "10.99.0.1", "192.168.1.1", "0.0.0.0"] {
let a: Ipv4Addr = addr.parse().unwrap();
assert_eq!(
table.longest_match_entry(a).map(|(_, v)| v),
table.longest_match(a),
);
}
}
#[test]
fn remove_returns_the_value() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.remove("10.0.0.0/8".parse().unwrap()), Some("ten"));
}
#[test]
fn remove_makes_prefix_unmatchable() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.remove("10.0.0.0/8".parse().unwrap());
assert_eq!(table.longest_match("10.0.0.1".parse().unwrap()), None);
}
#[test]
fn remove_nonexistent_prefix_returns_none() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.remove("192.168.0.0/16".parse().unwrap()), None);
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"ten")
);
}
#[test]
fn remove_specific_falls_back_to_general() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
table.remove("10.20.0.0/16".parse().unwrap());
assert_eq!(
table.longest_match("10.20.5.1".parse().unwrap()),
Some(&"broad")
);
assert_eq!(
table.longest_match("10.99.0.1".parse().unwrap()),
Some(&"broad")
);
}
#[test]
fn remove_general_keeps_specific() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
table.remove("10.0.0.0/8".parse().unwrap());
assert_eq!(
table.longest_match("10.20.5.1".parse().unwrap()),
Some(&"specific")
);
assert_eq!(table.longest_match("10.99.0.1".parse().unwrap()), None);
}
#[test]
fn remove_with_unmasked_prefix_finds_entry() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.remove("10.99.99.99/8".parse().unwrap()), Some("ten"));
}
#[test]
fn remove_default_route() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.remove("0.0.0.0/0".parse().unwrap());
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"ten")
);
assert_eq!(table.longest_match("192.168.1.1".parse().unwrap()), None);
}
#[test]
fn remove_all_prefixes_empties_table() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "a");
table.insert("10.20.0.0/16".parse().unwrap(), "b");
table.remove("10.0.0.0/8".parse().unwrap());
table.remove("10.20.0.0/16".parse().unwrap());
assert_eq!(table.longest_match("10.0.0.1".parse().unwrap()), None);
assert_eq!(table.longest_match("10.20.5.1".parse().unwrap()), None);
}
#[test]
fn get_returns_exact_entry() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
assert_eq!(table.get("10.0.0.0/8".parse().unwrap()), Some(&"broad"));
assert_eq!(
table.get("10.20.0.0/16".parse().unwrap()),
Some(&"specific")
);
}
#[test]
fn get_returns_none_for_covering_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
assert_eq!(table.get("10.20.0.0/16".parse().unwrap()), None);
}
#[test]
fn get_returns_none_for_missing_prefix() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert_eq!(table.get("10.0.0.0/8".parse().unwrap()), None);
}
#[test]
fn get_with_unmasked_prefix_finds_entry() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert_eq!(table.get("10.99.99.99/8".parse().unwrap()), Some(&"ten"));
}
#[test]
fn get_returns_none_after_remove() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.remove("10.0.0.0/8".parse().unwrap());
assert_eq!(table.get("10.0.0.0/8".parse().unwrap()), None);
}
#[test]
fn get_non_stride_aligned_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.64.0.0/10".parse().unwrap(), "slash10");
assert_eq!(table.get("10.64.0.0/10".parse().unwrap()), Some(&"slash10"));
assert_eq!(table.get("10.0.0.0/10".parse().unwrap()), None);
}
#[test]
fn contains_inserted_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert!(table.contains("10.0.0.0/8".parse().unwrap()));
}
#[test]
fn does_not_contain_uninserted_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert!(!table.contains("192.168.0.0/16".parse().unwrap()));
}
#[test]
fn does_not_contain_more_specific_prefix_that_was_not_inserted() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert!(!table.contains("10.20.0.0/16".parse().unwrap()));
}
#[test]
fn does_not_contain_broader_prefix_that_was_not_inserted() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
assert!(!table.contains("10.0.0.0/8".parse().unwrap()));
}
#[test]
fn contains_false_after_remove() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.remove("10.0.0.0/8".parse().unwrap());
assert!(!table.contains("10.0.0.0/8".parse().unwrap()));
}
#[test]
fn contains_with_unmasked_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
assert!(table.contains("10.99.99.99/8".parse().unwrap()));
}
#[test]
fn empty_table_contains_nothing() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert!(!table.contains("10.0.0.0/8".parse().unwrap()));
}
#[test]
fn prefix_length_not_multiple_of_stride() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.64.0.0/10".parse().unwrap(), "slash10");
assert_eq!(
table.longest_match("10.64.0.1".parse().unwrap()),
Some(&"slash10")
);
assert_eq!(
table.longest_match("10.128.0.1".parse().unwrap()),
Some(&"broad")
);
}
#[test]
fn prefix_length_one() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/1".parse().unwrap(), "low-half");
assert_eq!(
table.longest_match("1.2.3.4".parse().unwrap()),
Some(&"low-half")
);
assert_eq!(table.longest_match("128.0.0.1".parse().unwrap()), None);
}
#[test]
fn prefix_length_two() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("128.0.0.0/2".parse().unwrap(), "class-b");
assert_eq!(
table.longest_match("191.255.255.255".parse().unwrap()),
Some(&"class-b")
);
assert_eq!(table.longest_match("64.0.0.1".parse().unwrap()), None);
}
#[test]
fn prefix_length_three() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/3".parse().unwrap(), "slash3");
assert_eq!(
table.longest_match("10.0.0.1".parse().unwrap()),
Some(&"slash3")
);
assert_eq!(
table.longest_match("31.255.255.255".parse().unwrap()),
Some(&"slash3")
);
assert_eq!(table.longest_match("32.0.0.1".parse().unwrap()), None);
}
#[test]
fn remove_non_stride_aligned_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.64.0.0/10".parse().unwrap(), "slash10");
assert_eq!(
table.remove("10.64.0.0/10".parse().unwrap()),
Some("slash10")
);
assert_eq!(
table.longest_match("10.64.0.1".parse().unwrap()),
Some(&"broad")
);
assert!(!table.contains("10.64.0.0/10".parse().unwrap()));
}
#[test]
fn remove_non_stride_aligned_prefix_not_present() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
assert_eq!(table.remove("10.64.0.0/10".parse().unwrap()), None);
assert_eq!(
table.longest_match("10.64.0.1".parse().unwrap()),
Some(&"broad")
);
}
#[test]
fn contains_non_stride_aligned_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.64.0.0/10".parse().unwrap(), "slash10");
assert!(table.contains("10.64.0.0/10".parse().unwrap()));
assert!(!table.contains("10.0.0.0/10".parse().unwrap())); }
fn sorted_entries<A: IpAddress, V: Clone>(table: &RouteMap<A, V>) -> Vec<(String, V)> {
let mut entries: Vec<_> = table
.iter()
.map(|(p, v)| (format!("{}/{}", p.ip(), p.mask()), v.clone()))
.collect();
entries.sort_by_key(|(p, _)| p.clone());
entries
}
#[test]
fn iter_empty_table() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert_eq!(table.iter().count(), 0);
}
#[test]
fn iter_single_default_route() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
let entries = sorted_entries(&table);
assert_eq!(entries, vec![("0.0.0.0/0".to_string(), "default")]);
}
#[test]
fn iter_single_host_route() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.1/32".parse().unwrap(), "host");
let entries = sorted_entries(&table);
assert_eq!(entries, vec![("10.0.0.1/32".to_string(), "host")]);
}
#[test]
fn iter_multiple_prefixes_all_present() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("0.0.0.0/0".parse().unwrap(), "default");
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
table.insert("10.20.30.0/24".parse().unwrap(), "narrow");
let entries = sorted_entries(&table);
assert_eq!(entries.len(), 4);
assert!(entries.iter().any(|(_, v)| *v == "default"));
assert!(entries.iter().any(|(_, v)| *v == "broad"));
assert!(entries.iter().any(|(_, v)| *v == "specific"));
assert!(entries.iter().any(|(_, v)| *v == "narrow"));
}
#[test]
fn iter_reconstructs_prefix_correctly() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("192.168.1.0/24".parse().unwrap(), "subnet");
let (prefix, value) = table.iter().next().unwrap();
assert_eq!(format!("{}", prefix.ip()), "192.168.1.0");
assert_eq!(prefix.mask(), 24);
assert_eq!(*value, "subnet");
}
#[test]
fn iter_non_stride_aligned_prefix() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.64.0.0/10".parse().unwrap(), "slash10");
let entries = sorted_entries(&table);
assert_eq!(entries, vec![("10.64.0.0/10".to_string(), "slash10")]);
}
#[test]
fn iter_count_matches_insert_count() {
let mut table: RouteMap<Ipv4Addr, u32> = RouteMap::new();
let prefixes = [
"0.0.0.0/0",
"10.0.0.0/8",
"10.0.0.0/16",
"10.0.0.0/24",
"172.16.0.0/12",
"192.168.0.0/16",
"192.168.1.0/24",
"10.0.0.1/32",
];
for (i, p) in prefixes.iter().enumerate() {
table.insert(p.parse().unwrap(), i as u32);
}
assert_eq!(table.iter().count(), prefixes.len());
}
#[test]
fn iter_ipv6() {
let mut table: RouteMap<Ipv6Addr, &str> = RouteMap::new();
table.insert("::/0".parse().unwrap(), "default");
table.insert("2001:db8::/32".parse().unwrap(), "docs");
table.insert("2001:db8:1::/48".parse().unwrap(), "subnet");
let entries = sorted_entries(&table);
assert_eq!(entries.len(), 3);
assert!(entries.iter().any(|(_, v)| *v == "default"));
assert!(entries.iter().any(|(_, v)| *v == "docs"));
assert!(entries.iter().any(|(_, v)| *v == "subnet"));
}
#[test]
fn iter_multiple_external_children_exhausted() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.insert("64.0.0.0/8".parse().unwrap(), "sixty-four");
table.insert("192.168.0.0/16".parse().unwrap(), "office");
assert_eq!(table.iter().count(), 3);
}
#[test]
fn iter_after_remove_reflects_change() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "broad");
table.insert("10.20.0.0/16".parse().unwrap(), "specific");
table.remove("10.0.0.0/8".parse().unwrap());
let entries = sorted_entries(&table);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0], ("10.20.0.0/16".to_string(), "specific"));
}
#[test]
fn default_produces_empty_table() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::default();
assert_eq!(table.iter().count(), 0);
assert_eq!(table.longest_match("10.0.0.1".parse().unwrap()), None);
}
#[test]
fn clear_empties_table() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.insert("10.20.0.0/16".parse().unwrap(), "twenty");
table.clear();
assert!(table.is_empty());
assert_eq!(table.len(), 0);
assert_eq!(table.longest_match("10.0.0.1".parse().unwrap()), None);
}
#[test]
fn clear_then_reinsert_works() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.clear();
table.insert("192.168.0.0/16".parse().unwrap(), "home");
assert_eq!(table.len(), 1);
assert_eq!(
table.longest_match("192.168.1.1".parse().unwrap()),
Some(&"home")
);
}
#[test]
fn debug_empty_table() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert_eq!(format!("{:?}", table), "{}");
}
#[test]
fn debug_contains_prefix_and_value() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "datacenter");
let output = format!("{:?}", table);
assert!(output.contains("10.0.0.0/8"), "output was: {output}");
assert!(output.contains("datacenter"), "output was: {output}");
}
#[test]
fn debug_ipv6() {
let mut table: RouteMap<Ipv6Addr, u32> = RouteMap::new();
table.insert("2001:db8::/32".parse().unwrap(), 42);
let output = format!("{:?}", table);
assert!(output.contains("2001:db8::/32"), "output was: {output}");
assert!(output.contains("42"), "output was: {output}");
}
#[test]
fn collect_from_iterator() {
use ipnetx::prefix::IpPrefix;
let pairs: Vec<(IpPrefix<Ipv4Addr>, &str)> = vec![
("10.0.0.0/8".parse().unwrap(), "broad"),
("10.20.0.0/16".parse().unwrap(), "specific"),
];
let table: RouteMap<Ipv4Addr, &str> = pairs.into_iter().collect();
assert_eq!(
table.longest_match("10.20.5.1".parse().unwrap()),
Some(&"specific")
);
assert_eq!(
table.longest_match("10.99.0.1".parse().unwrap()),
Some(&"broad")
);
assert_eq!(table.iter().count(), 2);
}
#[test]
fn iter_collect_round_trip() {
let mut original: RouteMap<Ipv4Addr, u32> = RouteMap::new();
original.insert("0.0.0.0/0".parse().unwrap(), 0);
original.insert("10.0.0.0/8".parse().unwrap(), 1);
original.insert("10.20.0.0/16".parse().unwrap(), 2);
original.insert("192.168.0.0/16".parse().unwrap(), 3);
let restored: RouteMap<Ipv4Addr, u32> = original.iter().map(|(p, &v)| (p, v)).collect();
assert_eq!(restored.iter().count(), 4);
assert_eq!(
restored.longest_match("10.20.5.1".parse().unwrap()),
Some(&2)
);
assert_eq!(
restored.longest_match("10.99.0.1".parse().unwrap()),
Some(&1)
);
assert_eq!(
restored.longest_match("192.168.1.1".parse().unwrap()),
Some(&3)
);
assert_eq!(restored.longest_match("8.8.8.8".parse().unwrap()), Some(&0));
}
#[test]
fn len_empty_table() {
let table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
assert_eq!(table.len(), 0);
assert!(table.is_empty());
}
#[test]
fn len_tracks_inserts() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "a");
assert_eq!(table.len(), 1);
assert!(!table.is_empty());
table.insert("10.20.0.0/16".parse().unwrap(), "b");
assert_eq!(table.len(), 2);
}
#[test]
fn len_overwrite_does_not_increment() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "first");
table.insert("10.0.0.0/8".parse().unwrap(), "second");
assert_eq!(table.len(), 1);
}
#[test]
fn len_tracks_removes() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "a");
table.insert("10.20.0.0/16".parse().unwrap(), "b");
table.remove("10.0.0.0/8".parse().unwrap());
assert_eq!(table.len(), 1);
table.remove("10.20.0.0/16".parse().unwrap());
assert_eq!(table.len(), 0);
assert!(table.is_empty());
}
#[test]
fn len_remove_nonexistent_does_not_decrement() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "a");
table.remove("192.168.0.0/16".parse().unwrap());
assert_eq!(table.len(), 1);
}
#[test]
fn len_matches_iter_count() {
let mut table: RouteMap<Ipv4Addr, u32> = RouteMap::new();
let prefixes = ["0.0.0.0/0", "10.0.0.0/8", "10.20.0.0/16", "192.168.1.0/24"];
for (i, p) in prefixes.iter().enumerate() {
table.insert(p.parse().unwrap(), i as u32);
}
assert_eq!(table.len(), table.iter().count());
}
#[test]
fn into_iter_for_loop_syntax() {
let mut table: RouteMap<Ipv4Addr, &str> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), "ten");
table.insert("10.20.0.0/16".parse().unwrap(), "twenty");
let mut count = 0;
for (_prefix, _value) in &table {
count += 1;
}
assert_eq!(count, 2);
}
#[test]
fn into_iter_collect() {
let mut table: RouteMap<Ipv4Addr, u32> = RouteMap::new();
table.insert("10.0.0.0/8".parse().unwrap(), 1);
table.insert("192.168.0.0/16".parse().unwrap(), 2);
let entries: Vec<_> = (&table).into_iter().collect();
assert_eq!(entries.len(), 2);
}
}
#[cfg(test)]
mod prop_tests {
use super::*;
use proptest::prelude::*;
use std::net::{Ipv4Addr, Ipv6Addr};
fn v4(addr: u32, len: u8) -> IpPrefix<Ipv4Addr> {
IpPrefix::new(Ipv4Addr::from(addr), len).unwrap()
}
fn v6(addr: u128, len: u8) -> IpPrefix<Ipv6Addr> {
IpPrefix::new(Ipv6Addr::from(addr), len).unwrap()
}
fn mask4(len: u8) -> u32 {
if len == 0 {
0
} else {
!0u32 << (32 - len as u32)
}
}
fn mask6(len: u8) -> u128 {
if len == 0 {
0
} else {
!0u128 << (128 - len as u32)
}
}
proptest! {
#[test]
fn insert_implies_contains_and_get(
addr in any::<u32>(), len in 0u8..=32u8, val in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(addr, len), val);
prop_assert!(t.contains(v4(addr, len)));
prop_assert_eq!(t.get(v4(addr, len)), Some(&val));
}
#[test]
fn insert_longest_match_hits_network_addr(
addr in any::<u32>(), len in 0u8..=32u8, val in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(addr, len), val);
let network = Ipv4Addr::from(addr & mask4(len));
prop_assert_eq!(t.longest_match(network), Some(&val));
}
#[test]
fn overwrite_preserves_len_and_updates_value(
addr in any::<u32>(), len in 0u8..=32u8,
v1 in any::<u32>(), v2 in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(addr, len), v1);
t.insert(v4(addr, len), v2);
prop_assert_eq!(t.len(), 1);
prop_assert_eq!(t.get(v4(addr, len)), Some(&v2));
}
#[test]
fn unmasked_insert_equals_masked_insert(
addr in any::<u32>(), len in 0u8..=32u8, val in any::<u32>(),
) {
let mut t1: RouteMap<Ipv4Addr, u32> = RouteMap::new();
let mut t2: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t1.insert(v4(addr, len), val);
t2.insert(v4(addr & mask4(len), len), val);
prop_assert_eq!(t1.len(), t2.len());
for (prefix, &v) in t1.iter() {
prop_assert_eq!(t2.get(prefix), Some(&v));
}
}
#[test]
fn remove_clears_entry(
addr in any::<u32>(), len in 0u8..=32u8, val in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(addr, len), val);
prop_assert_eq!(t.remove(v4(addr, len)), Some(val));
prop_assert!(!t.contains(v4(addr, len)));
prop_assert_eq!(t.get(v4(addr, len)), None);
prop_assert_eq!(t.len(), 0);
}
#[test]
fn len_equals_iter_count(
ops in prop::collection::vec((any::<u32>(), 0u8..=32u8, any::<u32>()), 1..=30),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
for (addr, len, val) in ops {
t.insert(v4(addr, len), val);
}
prop_assert_eq!(t.len(), t.iter().count());
}
#[test]
fn more_specific_prefix_wins_lpm(
base in any::<u32>(),
broad_len in 0u8..=24u8,
extra in 1u8..=8u8,
) {
let specific_len = broad_len + extra;
prop_assume!(specific_len <= 32);
let network = Ipv4Addr::from(base & mask4(specific_len));
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(base, broad_len), 1);
t.insert(v4(base, specific_len), 2);
prop_assert_eq!(t.longest_match(network), Some(&2));
}
#[test]
fn remove_specific_falls_back_to_broad(
base in any::<u32>(),
broad_len in 0u8..=24u8,
extra in 1u8..=8u8,
) {
let specific_len = broad_len + extra;
prop_assume!(specific_len <= 32);
let network = Ipv4Addr::from(base & mask4(specific_len));
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(base, broad_len), 1);
t.insert(v4(base, specific_len), 2);
t.remove(v4(base, specific_len));
prop_assert_eq!(t.longest_match(network), Some(&1));
}
#[test]
fn default_route_matches_all_addresses(lookup in any::<u32>()) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(0, 0), 42);
prop_assert_eq!(t.longest_match(Ipv4Addr::from(lookup)), Some(&42));
}
#[test]
fn host_route_matches_only_exact_address(
host in any::<u32>(), other in any::<u32>(),
) {
prop_assume!(host != other);
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(host, 32), 99);
prop_assert_eq!(t.longest_match(Ipv4Addr::from(host)), Some(&99));
prop_assert_eq!(t.longest_match(Ipv4Addr::from(other)), None);
}
#[test]
fn iter_collect_roundtrip(
ops in prop::collection::vec((any::<u32>(), 0u8..=32u8, any::<u32>()), 1..=20),
) {
let mut original: RouteMap<Ipv4Addr, u32> = RouteMap::new();
for (addr, len, val) in ops {
original.insert(v4(addr, len), val);
}
let restored: RouteMap<Ipv4Addr, u32> =
original.iter().map(|(p, &v)| (p, v)).collect();
prop_assert_eq!(original.len(), restored.len());
for (prefix, &val) in original.iter() {
prop_assert_eq!(restored.get(prefix), Some(&val));
}
}
#[test]
fn ipv6_insert_implies_contains_and_get(
addr in any::<u128>(), len in 0u8..=128u8, val in any::<u32>(),
) {
let mut t: RouteMap<Ipv6Addr, u32> = RouteMap::new();
t.insert(v6(addr, len), val);
prop_assert!(t.contains(v6(addr, len)));
prop_assert_eq!(t.get(v6(addr, len)), Some(&val));
}
#[test]
fn ipv6_default_route_matches_all(lookup in any::<u128>()) {
let mut t: RouteMap<Ipv6Addr, u32> = RouteMap::new();
t.insert(v6(0, 0), 55);
prop_assert_eq!(t.longest_match(Ipv6Addr::from(lookup)), Some(&55));
}
#[test]
fn ipv6_more_specific_prefix_wins_lpm(
base in any::<u128>(),
broad_len in 0u8..=120u8,
extra in 1u8..=8u8,
) {
let specific_len = broad_len + extra;
prop_assume!(specific_len <= 128);
let network = Ipv6Addr::from(base & mask6(specific_len));
let mut t: RouteMap<Ipv6Addr, u32> = RouteMap::new();
t.insert(v6(base, broad_len), 1);
t.insert(v6(base, specific_len), 2);
prop_assert_eq!(t.longest_match(network), Some(&2));
}
#[test]
fn entry_value_agrees_with_longest_match(
ops in prop::collection::vec((any::<u32>(), 0u8..=32u8, any::<u32>()), 1..=30),
lookup in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
for (addr, len, val) in ops {
t.insert(v4(addr, len), val);
}
let a = Ipv4Addr::from(lookup);
prop_assert_eq!(t.longest_match_entry(a).map(|(_, v)| v), t.longest_match(a));
}
#[test]
fn entry_prefix_roundtrips_through_get(
ops in prop::collection::vec((any::<u32>(), 0u8..=32u8, any::<u32>()), 1..=30),
lookup in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
for (addr, len, val) in ops {
t.insert(v4(addr, len), val);
}
let a = Ipv4Addr::from(lookup);
if let Some((prefix, value)) = t.longest_match_entry(a) {
prop_assert_eq!(t.get(prefix), Some(value));
}
}
#[test]
fn entry_hits_own_network_with_prefix(
addr in any::<u32>(), len in 0u8..=32u8, val in any::<u32>(),
) {
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(addr, len), val);
let network = Ipv4Addr::from(addr & mask4(len));
prop_assert_eq!(
t.longest_match_entry(network),
Some((v4(addr & mask4(len), len), &val)),
);
}
#[test]
fn entry_returns_more_specific_prefix(
base in any::<u32>(),
broad_len in 0u8..=24u8,
extra in 1u8..=8u8,
) {
let specific_len = broad_len + extra;
prop_assume!(specific_len <= 32);
let network = Ipv4Addr::from(base & mask4(specific_len));
let mut t: RouteMap<Ipv4Addr, u32> = RouteMap::new();
t.insert(v4(base, broad_len), 1);
t.insert(v4(base, specific_len), 2);
prop_assert_eq!(
t.longest_match_entry(network),
Some((v4(base & mask4(specific_len), specific_len), &2)),
);
}
#[test]
fn ipv6_entry_hits_own_network_with_prefix(
addr in any::<u128>(), len in 0u8..=128u8, val in any::<u32>(),
) {
let mut t: RouteMap<Ipv6Addr, u32> = RouteMap::new();
t.insert(v6(addr, len), val);
let network = Ipv6Addr::from(addr & mask6(len));
prop_assert_eq!(
t.longest_match_entry(network),
Some((v6(addr & mask6(len), len), &val)),
);
}
}
}