#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
use std::collections::BTreeSet;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use bytes::Bytes;
use sipx_sip::error::UriError;
use sipx_sip::uri::Host;
fn hostport(text: &str) -> Result<Host, UriError> {
Host::parse_hostport(&Bytes::from(text.to_owned())).map(|(host, _port)| host)
}
fn address(text: &str) -> Result<Option<IpAddr>, UriError> {
hostport(text).map(|host| match host {
Host::Ip(ip) => Some(ip),
Host::Name(_) => None,
})
}
fn literal(text: &str) -> IpAddr {
IpAddr::V6(text.parse::<Ipv6Addr>().expect("a valid RFC 4291 address"))
}
#[test]
fn three_colon_table_rows_parse_exactly_as_the_spec_says() {
for (input, expected) in [
("[2001:db8::192.0.2.1]", "2001:db8::192.0.2.1"),
("[2001:db8:::192.0.2.1]", "2001:db8::192.0.2.1"),
("[1:2:3:4:5:::192.0.2.1]", "1:2:3:4:5::192.0.2.1"),
("[:::192.0.2.1]", "::192.0.2.1"),
] {
assert_eq!(
address(input),
Ok(Some(literal(expected))),
"§4.8 says {input} is {expected}"
);
}
for (input, expected) in [
("[2001:db8:::10]", UriError::Host),
("[2001:db8::::192.0.2.1]", UriError::Host),
("[2001:db8::1:::192.0.2.1]", UriError::Host),
("2001:db8:::192.0.2.1", UriError::Port),
("2001:db8::192.0.2.1", UriError::Port),
] {
assert_eq!(
hostport(input).err(),
Some(expected.clone()),
"§4.8 says {input} is {expected:?}"
);
}
}
const HEADS: &[&str] = &[
"",
":",
"::",
"0",
"2001",
"12345",
"g",
"abcd:ef01",
"2001:db8",
"2001:db8:",
"2001:db8::",
"fe80",
"fe80::",
"::1",
"1::",
"0:0:0:0:0:0",
"1:2:3:4:5",
"1:2:3:4:5:6",
"1:2:3:4:5:6:7",
"1:2:3:4:5:6:7:8",
"1:2:3:4:5:6:7:8:9",
];
const SEPARATORS: &[&str] = &[":", "::", ":::", "::::", ":::::", "::::::"];
const TAILS: &[&str] = &[
"",
"192.0.2.1",
"0.0.0.0",
"255.255.255.255",
"256.0.0.1",
"192.0.2",
"192.0.2.1.5",
"0192.0.2.1",
"192.0.2.01",
"10",
"ffff",
"1:2",
"192.0.2.1:5060",
"%eth0",
];
fn three_colon_runs(text: &str) -> usize {
text.as_bytes().windows(3).filter(|w| *w == b":::").count()
}
fn assert_is_the_rfc2373_derivation(reference: &str, inner: &str, got: Ipv6Addr) {
let runs = three_colon_runs(inner);
assert_eq!(
runs, 1,
"{reference}: accepted beyond RFC 4291 with {runs} ':::' runs; the derivation \
produces exactly one"
);
assert!(
!inner.contains("::::"),
"{reference}: accepted beyond RFC 4291 with four colons, which no RFC 2373 \
derivation produces"
);
let (hexseq, embedded) = inner
.split_once(":::")
.expect("just asserted there is exactly one ':::'");
assert!(
embedded.parse::<Ipv4Addr>().is_ok(),
"{reference}: accepted beyond RFC 4291 with {embedded:?} after the ':::'; only an \
embedded IPv4address is tolerated"
);
assert!(
!hexseq.contains("::"),
"{reference}: accepted beyond RFC 4291 with {hexseq:?} before the ':::'; RFC 2373's \
hexpart supplies the '::' itself, so the head must be a plain hexseq"
);
let rewritten = format!("{hexseq}::{embedded}");
let want = rewritten.parse::<Ipv6Addr>().unwrap_or_else(|e| {
panic!(
"{reference}: accepted beyond RFC 4291, but its two-colon form {rewritten:?} is \
not an address either: {e}"
)
});
assert_eq!(
got, want,
"{reference}: must mean exactly what its two-colon twin means"
);
}
#[test]
fn the_tolerance_admits_nothing_but_the_rfc2373_derivation() {
let mut examined = 0_usize;
let mut distinct = BTreeSet::new();
let mut plain = 0_usize;
let mut carve_outs = BTreeSet::new();
for head in HEADS {
for separator in SEPARATORS {
for tail in TAILS {
let inner = format!("{head}{separator}{tail}");
let reference = format!("[{inner}]");
examined += 1;
distinct.insert(inner.clone());
let std_says = inner.parse::<Ipv6Addr>().ok();
let sipx_says = match address(&reference) {
Ok(Some(IpAddr::V6(ip))) => Some(ip),
Ok(other) => panic!(
"{reference}: a bracketed reference is an IPv6 literal, got {other:?}"
),
Err(_) => None,
};
match (sipx_says, std_says) {
(Some(got), Some(want)) => {
plain += 1;
assert_eq!(
got, want,
"{reference}: already parsed under RFC 4291; the carve-out must not \
change what it means"
);
}
(Some(got), None) => {
carve_outs.insert(inner.clone());
assert_is_the_rfc2373_derivation(&reference, &inner, got);
}
(None, Some(want)) => panic!(
"{reference}: rejected, but RFC 4291 says it is {want} — the carve-out \
must only ever add to the accepted set"
),
(None, None) => {}
}
}
}
}
assert_eq!(
examined,
HEADS.len() * SEPARATORS.len() * TAILS.len(),
"every combination is examined"
);
assert_eq!(examined, 1764, "21 heads x 6 separators x 14 tails");
assert_eq!(
distinct.len(),
1428,
"1428 of those are distinct references"
);
assert_eq!(
plain, 86,
"the grid holds 86 combinations RFC 4291 already accepted, and the carve-out changed the \
meaning of none of them"
);
let expected: BTreeSet<String> = [
":::0.0.0.0",
":::192.0.2.1",
":::255.255.255.255",
"0:::0.0.0.0",
"0:::192.0.2.1",
"0:::255.255.255.255",
"1:::0.0.0.0",
"1:::192.0.2.1",
"1:::255.255.255.255",
"1:2:3:4:5:::0.0.0.0",
"1:2:3:4:5:::192.0.2.1",
"1:2:3:4:5:::255.255.255.255",
"2001:::0.0.0.0",
"2001:::192.0.2.1",
"2001:::255.255.255.255",
"2001:db8:::0.0.0.0",
"2001:db8:::192.0.2.1",
"2001:db8:::255.255.255.255",
"abcd:ef01:::0.0.0.0",
"abcd:ef01:::192.0.2.1",
"abcd:ef01:::255.255.255.255",
"fe80:::0.0.0.0",
"fe80:::192.0.2.1",
"fe80:::255.255.255.255",
]
.into_iter()
.map(str::to_owned)
.collect();
assert_eq!(
carve_outs, expected,
"the set sipx accepts beyond RFC 4291 must be exactly RFC 2373's one derivation"
);
}