use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use irontide_core::crc32c;
pub fn canonical_peer_priority(a: IpAddr, b: IpAddr) -> u32 {
let (a_norm, b_norm) = normalize_pair(a, b);
let (masked_a, masked_b) = mask_ips(a_norm, b_norm);
let (left, right) = if masked_a == masked_b {
order_ips(a_norm, b_norm)
} else {
order_ips(masked_a, masked_b)
};
let mut buf = [0u8; 32];
let len_a = write_ip_bytes(&left, &mut buf, 0);
let len_b = write_ip_bytes(&right, &mut buf, len_a);
crc32c(&buf[..len_a + len_b])
}
fn normalize_pair(a: IpAddr, b: IpAddr) -> (IpAddr, IpAddr) {
match (a, b) {
(IpAddr::V4(v4), IpAddr::V6(_)) => (IpAddr::V6(v4.to_ipv6_mapped()), b),
(IpAddr::V6(_), IpAddr::V4(v4)) => (a, IpAddr::V6(v4.to_ipv6_mapped())),
_ => (a, b),
}
}
fn mask_ips(a: IpAddr, b: IpAddr) -> (IpAddr, IpAddr) {
(mask_single(a), mask_single(b))
}
fn mask_single(ip: IpAddr) -> IpAddr {
match ip {
IpAddr::V4(v4) => {
let octets = v4.octets();
IpAddr::V4(Ipv4Addr::new(octets[0], octets[1], octets[2], 0))
}
IpAddr::V6(v6) => {
let octets = v6.octets();
let mut masked = [0u8; 16];
masked[..6].copy_from_slice(&octets[..6]);
IpAddr::V6(Ipv6Addr::from(masked))
}
}
}
fn order_ips(a: IpAddr, b: IpAddr) -> (IpAddr, IpAddr) {
let a_bytes = ip_to_bytes(a);
let b_bytes = ip_to_bytes(b);
if a_bytes <= b_bytes {
(a, b)
} else {
(b, a)
}
}
fn ip_to_bytes(ip: IpAddr) -> [u8; 16] {
match ip {
IpAddr::V4(v4) => {
let mut buf = [0u8; 16];
buf[12..16].copy_from_slice(&v4.octets());
buf
}
IpAddr::V6(v6) => v6.octets(),
}
}
fn write_ip_bytes(ip: &IpAddr, buf: &mut [u8], offset: usize) -> usize {
match ip {
IpAddr::V4(v4) => {
let octets = v4.octets();
buf[offset..offset + 4].copy_from_slice(&octets);
4
}
IpAddr::V6(v6) => {
let octets = v6.octets();
buf[offset..offset + 16].copy_from_slice(&octets);
16
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ip(s: &str) -> IpAddr {
s.parse().expect("valid test IP")
}
#[test]
fn priority_ipv4_different_subnets() {
let p1 = canonical_peer_priority(ip("123.213.32.10"), ip("98.76.54.32"));
let p2 = canonical_peer_priority(ip("123.213.32.99"), ip("98.76.54.1"));
assert_eq!(p1, p2, "different last octets in different /24s should produce same priority");
assert_ne!(p1, 0, "priority should be non-zero");
}
#[test]
fn priority_ipv4_same_subnet() {
let p1 = canonical_peer_priority(ip("10.0.0.1"), ip("10.0.0.2"));
let p2 = canonical_peer_priority(ip("10.0.0.1"), ip("10.0.0.3"));
assert_ne!(p1, p2, "same /24 should use full IPs, giving different priorities");
}
#[test]
fn priority_ipv6_different_subnets() {
let p1 = canonical_peer_priority(
ip("2001:db8:1::1"),
ip("2001:db8:2::1"),
);
let p2 = canonical_peer_priority(
ip("2001:db8:1::ffff"),
ip("2001:db8:2::abcd"),
);
assert_eq!(p1, p2, "different suffixes in different /48s should produce same priority");
assert_ne!(p1, 0);
}
#[test]
fn priority_ipv6_same_subnet() {
let p1 = canonical_peer_priority(
ip("2001:db8:1::1"),
ip("2001:db8:1::2"),
);
let p2 = canonical_peer_priority(
ip("2001:db8:1::1"),
ip("2001:db8:1::3"),
);
assert_ne!(p1, p2, "same /48 should use full IPs, giving different priorities");
}
#[test]
fn priority_is_symmetric() {
let pairs = [
(ip("123.213.32.10"), ip("98.76.54.32")),
(ip("10.0.0.1"), ip("10.0.0.2")),
(ip("2001:db8:1::1"), ip("2001:db8:2::2")),
(ip("192.168.1.1"), ip("::ffff:192.168.1.2")),
];
for (a, b) in &pairs {
assert_eq!(
canonical_peer_priority(*a, *b),
canonical_peer_priority(*b, *a),
"priority must be symmetric for {a} and {b}"
);
}
}
#[test]
fn priority_deterministic() {
let a = ip("55.55.55.55");
let b = ip("66.66.66.66");
let first = canonical_peer_priority(a, b);
for _ in 0..100 {
assert_eq!(canonical_peer_priority(a, b), first, "must be deterministic");
}
}
#[test]
fn priority_mixed_v4_v6() {
let v4 = ip("192.168.1.1");
let v6 = ip("2001:db8::1");
let p = canonical_peer_priority(v4, v6);
assert_ne!(p, 0, "mixed v4/v6 should produce a non-zero priority");
assert_eq!(
canonical_peer_priority(v4, v6),
canonical_peer_priority(v6, v4),
);
}
#[test]
fn priority_known_vectors() {
let a = ip("123.213.32.10");
let b = ip("98.76.54.32");
assert_eq!(canonical_peer_priority(a, b), 0xCDDE_2768);
assert_eq!(
canonical_peer_priority(ip("10.0.0.1"), ip("10.0.0.2")),
0xCC62_1322,
);
}
}