use std::ops::Range;
use tinyvec::ArrayVec;
#[derive(Debug, Clone)]
pub(crate) enum Segment {
Num(Range<usize>),
Colons(Range<usize>),
}
impl Segment {
fn is_colons(&self) -> bool {
matches!(self, Segment::Colons(_))
}
fn range(&self) -> &Range<usize> {
match self {
Segment::Num(range) | Segment::Colons(range) => range,
}
}
fn range_mut(&mut self) -> &mut Range<usize> {
match self {
Segment::Num(range) | Segment::Colons(range) => range,
}
}
}
impl Default for Segment {
fn default() -> Self {
Self::Colons(0..0)
}
}
fn ipv6_split_iter_rev(ip: &str) -> impl Iterator<Item = Segment> + '_ {
let mut colons = ip
.bytes()
.enumerate()
.rev()
.map(|(pos, b)| (pos, b == b':'));
let mut last = None::<Segment>;
std::iter::from_fn(move || {
for (pos, is_colon) in colons.by_ref() {
match &mut last {
Some(segment) if is_colon == segment.is_colons() => {
segment.range_mut().start -= 1
}
_ => {
let range = pos..pos + 1;
if let Some(segment) = last.replace(if is_colon {
Segment::Colons(range)
} else {
Segment::Num(range)
}) {
return Some(segment);
}
}
}
}
last.take()
})
}
#[derive(Debug)]
pub(crate) enum Error {
TooManyConsecutiveDigits(Range<usize>),
TooManyConsecutiveColons(Range<usize>),
MoreThanOneDoubleColon,
TooManySegments,
TooFewSegments(usize),
InvalidDigit(Range<usize>),
TrailingColon,
LeadingColon,
}
type SegmentVec = ArrayVec<[Segment; 9]>;
pub(crate) fn parse_ipv6_rev(ip: &str) -> Result<SegmentVec, Error> {
let mut found_double_colon = false;
let mut number_segment_count = 0;
use Error::*;
use Segment::*;
let segments = ipv6_split_iter_rev(ip)
.filter(|segment| {
!matches!(segment, Colons(range) if range.len() == 1 && !(range.start == 0 || range.end == ip.len()))
}).map(|segment| {
match segment {
Num(ref range) => {
if range.len() > 4 {
return Err(TooManyConsecutiveDigits(range.clone()));
} else if !ip[range.clone()]
.bytes()
.all(|b| b.is_ascii_hexdigit())
{
return Err(InvalidDigit(range.clone()));
}
number_segment_count += 1;
}
Colons(ref range) => match range.len() {
1 => {
if range.start == 0 {
return Err(LeadingColon);
} else if range.end == ip.len() {
return Err(TrailingColon);
}
}
2 => {
if found_double_colon {
return Err(MoreThanOneDoubleColon);
} else {
found_double_colon = true;
number_segment_count += 1;
}
}
_ => return Err(TooManyConsecutiveColons(range.clone())),
},
}
Ok(segment)
})
.take(9);
let segments = segments.collect::<Result<SegmentVec, _>>()?;
if number_segment_count < 8 && !found_double_colon {
Err(TooFewSegments(number_segment_count))
} else if segments.len() > 8
|| segments
.last()
.map(|segment| segment.range().start != 0)
.unwrap_or(false)
{
Err(TooManySegments)
} else {
Ok(segments)
}
}
#[test]
fn parser_rejects_invalid_ipv6_addresses() {
macro_rules! rep {
($thing:expr; $count:expr) => {
vec![$thing; $count].join(":")
};
}
for input in [
":::",
":::1",
"1:::",
"1:::1",
&(rep!["01"; 8] + ":"),
&(":".to_string() + &rep!["01"; 8]),
&rep!["01"; 7],
&rep!["01"; 9],
&("::".to_string() + &rep!["01"; 8]),
&(rep!["1"; 8] + "::"),
&("::".to_string() + &rep!["01"; 6] + "::"),
&("::".to_string() + &rep!["01"; 2] + "::" + &rep!["1"; 3] + "::"),
&(rep!["01"; 2] + "::" + &rep!["1"; 2] + "::" + &rep!["1"; 2]),
"00001:1:1:1:1:1:1:1",
"1:1:1:1:1:1:1:00000",
&(" ".to_string() + &rep!["01"; 7]),
&(" :".to_string() + &rep!["01"; 8]),
&(rep!["01"; 7] + " "),
&rep!["g"; 8],
] {
let parsed = parse_ipv6_rev(input).map(|vec| {
vec.into_iter()
.map(|segment| input.get(segment.range().clone()))
.collect::<Vec<_>>()
});
assert!(
parsed.is_err(),
"{:?}\nshould not be successfully parsed, but it yielded the following numerical segments:\n{:?}",
input,
parsed
);
}
}