use core::net::IpAddr;
use crate::{
BaseIndex,
iptrie::{MAX_DEPTH, StridePath},
};
const DEPTH_MASK: usize = MAX_DEPTH - 1;
#[inline]
pub const fn is_fringe(depth: usize, prefix: &ipnet::IpNet) -> bool {
let (octet_count, overflow_bits) = stride_count_and_overflow(prefix);
depth == octet_count - 1 && overflow_bits == 0
}
pub fn prefix_from_path(
path_octets: &[u8],
depth: usize,
ipv4: bool,
idx: BaseIndex,
) -> ipnet::IpNet {
let depth = depth & DEPTH_MASK;
let mut path = StridePath::default();
let (octet, idx_len) = idx.prefix();
let octet_depth = path_octets.len().min(depth);
path[..octet_depth].copy_from_slice(&path_octets[..octet_depth]);
path[depth] = octet;
ipnet::IpNet::new_assert(ip_from_path(path, ipv4), depth as u8 * 8 + idx_len)
}
pub fn fringe_prefix(path_octets: &[u8], depth: usize, ipv4: bool, last_octet: u8) -> ipnet::IpNet {
let depth = depth & DEPTH_MASK;
let mut path = [0u8; MAX_DEPTH];
let mut i = 0;
while i < MAX_DEPTH {
path[i] = match i {
x if x > depth => 0,
x if x == depth => last_octet,
x if x < path_octets.len() => path_octets[i],
_ => 0,
};
i += 1;
}
let ip = ip_from_path(path, ipv4);
let bits = (depth + 1) * 8;
ipnet::IpNet::new_assert(ip, bits as _)
}
fn ip_from_path(path: StridePath, is4: bool) -> IpAddr {
if is4 {
core::net::Ipv4Addr::from(*path.first_chunk::<4>().unwrap()).into()
} else {
core::net::Ipv6Addr::from(path).into()
}
}
pub const fn stride_count_and_overflow(prefix: &ipnet::IpNet) -> (usize, u8) {
let len = match prefix {
ipnet::IpNet::V4(v4) => v4.prefix_len(),
ipnet::IpNet::V6(v6) => v6.prefix_len(),
};
((len / 8) as _, len % 8)
}
#[allow(unsafe_code)]
pub fn ip_octets(ip: &IpAddr) -> &[u8] {
static_assertions::const_assert_eq!(4, size_of::<core::net::Ipv4Addr>());
static_assertions::const_assert_eq!(16, size_of::<core::net::Ipv6Addr>());
match ip {
IpAddr::V4(v4) => unsafe { core::slice::from_raw_parts(v4 as *const _ as *const u8, 4) },
IpAddr::V6(v6) => unsafe { core::slice::from_raw_parts(v6 as *const _ as *const u8, 16) },
}
}
#[cfg(test)]
mod test {
use core::net::Ipv6Addr;
use super::*;
#[macro_export]
macro_rules! pfx {
($pfx:expr) => {
<::ipnet::IpNet as ::core::str::FromStr>::from_str($pfx)
.unwrap()
.trunc()
};
}
#[test]
fn bart_examples_stride_count_and_overflow() {
assert_eq!((0, 0), stride_count_and_overflow(&pfx!("0.0.0.0/0")),);
assert_eq!((4, 0), stride_count_and_overflow(&pfx!("0.0.0.0/32")),);
assert_eq!((0, 7), stride_count_and_overflow(&pfx!("10.0.0.0/7")),);
assert_eq!((1, 6), stride_count_and_overflow(&pfx!("10.20.0.0/14")),);
assert_eq!((3, 0), stride_count_and_overflow(&pfx!("10.20.30.0/24")),);
assert_eq!((3, 7), stride_count_and_overflow(&pfx!("10.20.30.40/31")),);
assert_eq!((0, 0), stride_count_and_overflow(&pfx!("::/0")),);
assert_eq!((16, 0), stride_count_and_overflow(&pfx!("::/128"),),);
assert_eq!((3, 7), stride_count_and_overflow(&pfx!("2001:db8::/31"),),);
}
const TEST_IPV6: [u8; 16] = {
let mut ret = [0u8; 16];
ret.first_chunk_mut::<4>()
.unwrap()
.copy_from_slice(&[0x20, 0x01, 0x0d, 0xb8]);
ret
};
fn test_ipv6_with_octets(idx: &[usize]) -> [u8; 16] {
let mut ret = TEST_IPV6;
for &idx in idx {
ret[idx] = 1;
}
ret
}
#[test]
fn bart_examples_cidr_from_path() {
assert_eq!(
pfx!("0.0.0.0/0"),
prefix_from_path(&[], 0, true, BaseIndex::new(1)),
);
assert_eq!(
pfx!("128.0.0.0/1"),
prefix_from_path(&[], 0, true, BaseIndex::new(3)),
);
assert_eq!(
pfx!("192.128.0.0/9"),
prefix_from_path(&[192, 168, 0, 0,], 1, true, BaseIndex::new(3)),
);
assert_eq!(
pfx!("10.0.224.0/19"),
prefix_from_path(&[10, 0, 1, 0,], 2, true, BaseIndex::new(15)),
);
assert_eq!(
pfx!("128.0.0.0/1"),
prefix_from_path(&[192, 168, 1, 0], 32, true, BaseIndex::new(3)),
);
assert_eq!(
pfx!("0.0.0.0/0"),
prefix_from_path(&[], 0, true, BaseIndex::new(1)),
);
assert_eq!(
pfx!("10.20.244.0/19"),
prefix_from_path(
&[10, 20, 30, 40, 50, 60, 70, 80, 90],
2,
true,
BaseIndex::new(15)
),
);
assert_eq!(
pfx!("::/0"),
prefix_from_path(&[], 0, false, BaseIndex::new(1)),
);
assert_eq!(
pfx!("20f8::/13"),
prefix_from_path(&TEST_IPV6[..8], 1, false, BaseIndex::new(63)),
);
assert_eq!(
pfx!("2001:db8:0:fc::/62"),
prefix_from_path(
&test_ipv6_with_octets(&[7])[..8],
7,
false,
BaseIndex::new(127)
),
);
assert_eq!(
pfx!("2001:db8:0:1::fe/127"),
prefix_from_path(
&test_ipv6_with_octets(&[7, 15]),
15,
false,
BaseIndex::new(255)
),
);
assert_eq!(
pfx!("c000::/2"),
prefix_from_path(&[0x20, 0x01, 0x0d, 0xb8], 48, false, BaseIndex::new(7)),
);
}
#[test]
fn bart_examples_cidr_for_fringe() {
assert_eq!(pfx!("0.0.0.0/8"), fringe_prefix(&[10, 0, 0, 0], 0, true, 0),);
assert_eq!(
pfx!("192.0.0.0/16"),
fringe_prefix(&[192, 168, 0, 0], 1, true, 0),
);
assert_eq!(pfx!("0.0.0.0/8"), fringe_prefix(&[], 0, true, 0),);
assert_eq!(
pfx!("50.0.0.0/8"),
fringe_prefix(&[10, 20, 30, 40], 32, true, 50),
);
assert_eq!(pfx!("::/8"), fringe_prefix(&TEST_IPV6, 0, false, 0).trunc(),);
assert_eq!(
pfx!("2000::/16"),
fringe_prefix(&TEST_IPV6, 1, false, 0).trunc(),
);
assert_eq!(
pfx!("2001:db8::/64"),
fringe_prefix(&test_ipv6_with_octets(&[7]), 7, false, 0).trunc(),
);
assert_eq!(
pfx!("2001:db8:0:1::/128"),
fringe_prefix(&test_ipv6_with_octets(&[7, 15]), 15, false, 0).trunc(),
);
assert_eq!(
pfx!("fe80:0:0:ff::/64"),
fringe_prefix(
&Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0).octets(),
7,
false,
0xff,
)
.trunc(),
);
assert_eq!(pfx!("::/8"), fringe_prefix(&[], 0, false, 0),);
assert_eq!(
pfx!("ac10:6300::/24"),
fringe_prefix(
&[
0xac, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e,
],
2,
false,
0x63
),
);
}
#[test]
fn bart_examples_test_is_fringe() {
assert!(is_fringe(0, &pfx!("10.0.0.0/8")));
assert!(is_fringe(1, &pfx!("192.168.0.0/16")));
assert!(is_fringe(2, &pfx!("10.0.1.0/24")));
assert!(is_fringe(3, &pfx!("192.168.1.1/32")));
assert!(!is_fringe(1, &pfx!("192.128.0.0/9")));
assert!(!is_fringe(3, &pfx!("10.0.0.1/25")));
assert!(!is_fringe(0, &pfx!("192.168.0.0/16")));
assert!(!is_fringe(1, &pfx!("10.0.0.0/8")));
assert!(is_fringe(0, &pfx!("2001::/8")));
assert!(is_fringe(1, &pfx!("2001:db8::/16")));
assert!(is_fringe(7, &pfx!("2001:db8::/64")));
assert!(is_fringe(15, &pfx!("2001:db8::1/128")));
assert!(!is_fringe(1, &pfx!("2000::/9")));
assert!(!is_fringe(8, &pfx!("2001:db8::/65")));
assert!(!is_fringe(0, &pfx!("2001:db8::/16")));
assert!(!is_fringe(6, &pfx!("2001:db8::/64")));
}
}