Skip to content
This repository was archived by the owner on May 9, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ readme = "README.md"
keywords = ["ip", "networking", "trie", "cidr"]
categories = ["data-structures", "network-programming"]

[dev-dependencies]
rand = "0.7.2"

[badges]
travis-ci = { repository = "hroi/treebitmap" }

Expand Down
209 changes: 209 additions & 0 deletions tests/rand_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
extern crate rand;
extern crate treebitmap;

use std::mem;
use std::net::{Ipv4Addr, Ipv6Addr};

use self::rand::{thread_rng, Rng};

use treebitmap::address::Address;
use treebitmap::*;

const NUMBER_OF_ITERS: usize = 10; // number of times to run each test
const NUMBER_OF_PEERS: usize = 64; // number of distinct values
const NUMBER_OF_ADDRESS: usize = 100; // number of subnets in trie
const NUMBER_OF_LOOKUPS: usize = 1000; // number of lookups per test

/* Randomized tests against a naive implemenation of a prefix trie.
*/

struct SlowNode<T, A: Address> {
value: T,
ip: A,
masklen: u32,
}

struct SlowRouter<T, A: Address> {
nodes: Vec<SlowNode<T, A>>,
}

impl<T, A: Address> SlowNode<T, A> {
fn new(ip: A, masklen: u32, value: T) -> SlowNode<T, A> {
SlowNode { ip, masklen, value }
}

fn matches(&self, ip: A) -> bool {
let mut remain = self.masklen;
for (n1, n2) in ip
.nibbles()
.as_ref()
.iter()
.zip(self.ip.nibbles().as_ref().iter())
{
let check = if remain > 4 { 4 } else { remain };
let bits1 = n1 >> (4 - check);
let bits2 = n2 >> (4 - check);
if bits1 != bits2 {
return false;
}
remain -= check;
}
true
}

fn in_range<R>(&self, rng: &mut R) -> A
where
R: Rng,
{
// create mutable copy of nibs
let org = self.ip.nibbles();
let mut nibs = vec![0u8; org.as_ref().len()];
nibs.copy_from_slice(org.as_ref());

// flip bits after masklen
for i in 0..nibs.len() * 4 {
if i >= self.masklen as usize {
let idx = i % 4;
let bit: u8 = rng.gen();
let bit = (bit & 0x80) >> 4; // top-most bit of a nibble
nibs[i / 4] ^= bit >> idx;
assert!(nibs[i / 4] < 16);
}
}

// santify check
let addr = A::from_nibbles(nibs.as_ref());
assert!(self.matches(addr), "generated IP should match subnet");
addr
}
}

impl<T, A: Address> PartialEq for SlowNode<T, A> {
fn eq(&self, rhs: &SlowNode<T, A>) -> bool {
self.ip.nibbles().as_ref() == rhs.ip.nibbles().as_ref() && self.masklen == rhs.masklen
}
}

impl<T, A: Address> SlowRouter<T, A> {
fn new() -> SlowRouter<T, A> {
SlowRouter { nodes: vec![] }
}

fn insert(&mut self, ip: A, masklen: u32, value: T) -> Option<T> {
let n1 = SlowNode::new(ip, masklen, value);
for n2 in self.nodes.iter_mut() {
if n1 == *n2 {
let old = mem::replace(n2, n1);
return Some(old.value);
}
}
self.nodes.push(n1);
None
}

fn longest_match(&self, ip: A) -> Option<(A, u32, &T)> {
let mut res: Option<(A, u32, &T)> = None;
for node in self.nodes.iter() {
if node.matches(ip) {
match res {
None => res = Some((node.ip, node.masklen, &node.value)),
Some((_, l, _)) => {
if l < node.masklen {
res = Some((node.ip, node.masklen, &node.value));
}
}
}
}
}
res
}

fn any<R>(&self, rng: &mut R) -> A
where
R: Rng,
{
let idx = rng.gen_range(0, self.nodes.len());
self.nodes[idx].in_range(rng)
}
}

#[test]
fn ipv6_random_test() {
for _ in 0..NUMBER_OF_ITERS {
let mut tbl = IpLookupTable::new();
let mut slw = SlowRouter::new();
let mut rng = thread_rng();

macro_rules! ipv6 {
() => {{
Ipv6Addr::new(
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
)
}};
}

for _ in 0..NUMBER_OF_ADDRESS {
let masklen = rng.gen_range(0, 128);
let ip = ipv6!().mask(masklen);
let value = rng.gen_range(0, NUMBER_OF_PEERS);
tbl.insert(ip, masklen, value);
slw.insert(ip, masklen, value);
}

for _ in 0..NUMBER_OF_LOOKUPS {
let ip = if rng.gen() {
slw.any(&mut rng)
} else {
ipv6!()
};
assert_eq!(
tbl.longest_match(ip),
slw.longest_match(ip),
"naive list implemenation and trie does not agree"
);
}
}
}

#[test]
fn ipv4_random_test() {
for _ in 0..NUMBER_OF_ITERS {
let mut tbl = IpLookupTable::new();
let mut slw = SlowRouter::new();
let mut rng = thread_rng();

macro_rules! ipv4 {
() => {{
Ipv4Addr::new(rng.gen(), rng.gen(), rng.gen(), rng.gen())
}};
}

for _ in 0..NUMBER_OF_ADDRESS {
let masklen = rng.gen_range(0, 32);
let ip = ipv4!().mask(masklen);
let value = rng.gen_range(0, NUMBER_OF_PEERS);
tbl.insert(ip, masklen, value);
slw.insert(ip, masklen, value);
}

for _ in 0..NUMBER_OF_LOOKUPS {
let ip = if rng.gen() {
slw.any(&mut rng)
} else {
ipv4!()
};
assert_eq!(
tbl.longest_match(ip),
slw.longest_match(ip),
"naive list implemenation and trie does not agree"
);
}
}
}
165 changes: 165 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

extern crate treebitmap;

mod rand_test;

use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use treebitmap::*;
Expand Down Expand Up @@ -186,3 +188,166 @@ fn issue_7() {

println!("len: {}", table.len());
}

#[test]
fn ipv4_tests() {
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let g = 6;
let h = 7;

let mut table: IpLookupTable<Ipv4Addr, usize> = IpLookupTable::new();

macro_rules! insert {
($v:expr, $o1:expr, $o2:expr, $o3:expr, $o4:expr, $cidr:expr) => {
table.insert(Ipv4Addr::new($o1, $o2, $o3, $o4), $cidr, $v);
};
}

macro_rules! assert_match {
($v:expr, $o1:expr, $o2:expr, $o3:expr, $o4:expr) => {
let res = table
.longest_match(Ipv4Addr::new($o1, $o2, $o3, $o4))
.unwrap();
assert_eq!(*res.2, $v);
};
}

macro_rules! assert_not_match {
($v:expr, $o1:expr, $o2:expr, $o3:expr, $o4:expr) => {
let res = table
.longest_match(Ipv4Addr::new($o1, $o2, $o3, $o4))
.unwrap();
assert_ne!(*res.2, $v);
};
}

macro_rules! remove_by_value {
($v: expr) => {
let mut remove = vec![];
for (ip, cidr, v) in table.iter_mut() {
if *v == $v {
remove.push((ip, cidr));
}
}
for (ip, cidr) in remove {
table.remove(ip, cidr);
}
};
}

insert!(a, 192, 168, 4, 0, 24);
insert!(b, 192, 168, 4, 4, 32);
insert!(c, 192, 168, 0, 0, 16);
insert!(d, 192, 95, 5, 64, 27);
insert!(c, 192, 95, 5, 65, 27);
insert!(e, 0, 0, 0, 0, 0);
insert!(g, 64, 15, 112, 0, 20);
insert!(h, 64, 15, 123, 128, 25);
insert!(a, 10, 0, 0, 0, 25);
insert!(b, 10, 0, 0, 128, 25);
insert!(a, 10, 1, 0, 0, 30);
insert!(b, 10, 1, 0, 4, 30);
insert!(c, 10, 1, 0, 8, 29);
insert!(d, 10, 1, 0, 16, 29);

assert_match!(a, 192, 168, 4, 20);
assert_match!(a, 192, 168, 4, 0);
assert_match!(b, 192, 168, 4, 4);
assert_match!(c, 192, 168, 200, 182);
assert_match!(c, 192, 95, 5, 68);
assert_match!(e, 192, 95, 5, 96);
assert_match!(g, 64, 15, 116, 26);
assert_match!(g, 64, 15, 127, 3);

insert!(a, 1, 0, 0, 0, 32);
insert!(a, 64, 0, 0, 0, 32);
insert!(a, 128, 0, 0, 0, 32);
insert!(a, 192, 0, 0, 0, 32);
insert!(a, 255, 0, 0, 0, 32);

assert_match!(a, 1, 0, 0, 0);
assert_match!(a, 64, 0, 0, 0);
assert_match!(a, 128, 0, 0, 0);
assert_match!(a, 192, 0, 0, 0);
assert_match!(a, 255, 0, 0, 0);

remove_by_value!(a);

assert_not_match!(a, 1, 0, 0, 0);
assert_not_match!(a, 64, 0, 0, 0);
assert_not_match!(a, 128, 0, 0, 0);
assert_not_match!(a, 192, 0, 0, 0);
assert_not_match!(a, 255, 0, 0, 0);
}

#[test]
fn ipv6_tests() {
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
let h = 8;

let mut table: IpLookupTable<Ipv6Addr, usize> = IpLookupTable::new();

macro_rules! ipv6 {
($o1:expr, $o2:expr, $o3:expr, $o4:expr) => {{
let o1: u32 = $o1;
let o2: u32 = $o2;
let o3: u32 = $o3;
let o4: u32 = $o4;
Ipv6Addr::new(
(o1 >> 16) as u16,
(o1 & 0xffff) as u16,
(o2 >> 16) as u16,
(o2 & 0xffff) as u16,
(o3 >> 16) as u16,
(o3 & 0xffff) as u16,
(o4 >> 16) as u16,
(o4 & 0xffff) as u16,
)
}};
}

macro_rules! insert {
($v:expr, $o1:expr, $o2:expr, $o3:expr, $o4:expr, $cidr:expr) => {
table.insert(ipv6!($o1, $o2, $o3, $o4), $cidr, $v);
};
}

macro_rules! assert_match {
($v:expr, $o1:expr, $o2:expr, $o3:expr, $o4:expr) => {
let res = table.longest_match(ipv6!($o1, $o2, $o3, $o4)).unwrap();
assert_eq!(*res.2, $v);
};
}

insert!(d, 0x26075300, 0x60006b00, 0, 0xc05f0543, 128);
insert!(c, 0x26075300, 0x60006b00, 0, 0, 64);
insert!(e, 0, 0, 0, 0, 0);
insert!(f, 0, 0, 0, 0, 0);
insert!(g, 0x24046800, 0, 0, 0, 32);
insert!(h, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef, 64);
insert!(a, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef, 128);
insert!(c, 0x24446800, 0x40e40800, 0xdeaebeef, 0xdefbeef, 128);
insert!(b, 0x24446800, 0xf0e40800, 0xeeaebeef, 0, 98);

assert_match!(d, 0x26075300, 0x60006b00, 0, 0xc05f0543);
assert_match!(c, 0x26075300, 0x60006b00, 0, 0xc02e01ee);
assert_match!(f, 0x26075300, 0x60006b01, 0, 0);
assert_match!(g, 0x24046800, 0x40040806, 0, 0x1006);
assert_match!(g, 0x24046800, 0x40040806, 0x1234, 0x5678);
assert_match!(f, 0x240467ff, 0x40040806, 0x1234, 0x5678);
assert_match!(f, 0x24046801, 0x40040806, 0x1234, 0x5678);
assert_match!(h, 0x24046800, 0x40040800, 0x1234, 0x5678);
assert_match!(h, 0x24046800, 0x40040800, 0, 0);
assert_match!(h, 0x24046800, 0x40040800, 0x10101010, 0x10101010);
assert_match!(a, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef);
}