use core::{fmt, net, slice, ptr};
enum FamilyType {
Unknown,
V4,
V6,
}
#[derive(Debug)]
enum ParserState {
Initial,
Digit,
V4Sep,
V6Sep,
}
mod flag {
pub const IS_IPV6_ZERO_SKIP: u8 = 0b010;
pub const IS_IPV6_SEP_INITIAL: u8 = 0b100;
}
struct Parser<'a> {
state: ParserState,
family: FamilyType,
flags: u8,
components_size: u8,
components: [u16; 8],
zero_component_start: u8,
start_digit_position: usize,
text: &'a [u8],
}
impl<'a> Parser<'a> {
const IPV4_LEN: u8 = 4;
const IPV6_LEN: u8 = 8;
#[inline(always)]
const fn get_current_component(&self, component_sep_pos: usize) -> &'a str {
unsafe {
core::str::from_utf8_unchecked(
slice::from_raw_parts(self.text.as_ptr().add(self.start_digit_position), component_sep_pos.saturating_sub(self.start_digit_position))
)
}
}
const fn extract_v4_component(&mut self, component_sep_pos: usize) -> Option<ParseError<'a>> {
let text = self.get_current_component(component_sep_pos);
if self.components_size >= Self::IPV4_LEN {
return Some(ParseError::Ipv4InvalidComponentSize(self.components_size.saturating_add(1)));
}
match u8::from_str_radix(text, 10) {
Ok(component) => {
self.components[self.components_size as usize] = component as _;
self.components_size = self.components_size.saturating_add(1);
self.start_digit_position = 0;
None
},
Err(_) => Some(ParseError::InvalidComponent(text)),
}
}
const fn extract_v6_component(&mut self, component_sep_pos: usize) -> Option<ParseError<'a>> {
let text = self.get_current_component(component_sep_pos);
if self.components_size >= Self::IPV6_LEN {
return Some(ParseError::Ipv6InvalidComponentSize(self.components_size.saturating_add(1)));
}
match u16::from_str_radix(text, 16) {
Ok(component) => {
self.components[self.components_size as usize] = component;
self.components_size = self.components_size.saturating_add(1);
self.start_digit_position = 0;
None
},
Err(_) => Some(ParseError::InvalidComponent(text)),
}
}
const fn read_ip_at_last(&mut self, component_sep_pos: usize) -> Result<net::IpAddr, ParseError<'a>> {
match self.family {
FamilyType::V4 => {
if let Some(error) = self.extract_v4_component(component_sep_pos) {
return Err(error)
}
if self.components_size == Self::IPV4_LEN {
Ok(
net::IpAddr::V4(
net::Ipv4Addr::new(
self.components[0] as _,
self.components[1] as _,
self.components[2] as _,
self.components[3] as _
)
)
)
} else {
return Err(ParseError::Ipv4InvalidComponentSize(self.components_size));
}
}
FamilyType::V6 => {
if let Some(error) = self.extract_v6_component(component_sep_pos) {
return Err(error)
}
self.read_ipv6()
}
FamilyType::Unknown => match self.state {
ParserState::Initial => Err(ParseError::MissingIp),
_ => Err(ParseError::InvalidIp),
}
}
}
const fn read_ipv6(&mut self) -> Result<net::IpAddr, ParseError<'a>> {
if self.components_size > Self::IPV6_LEN {
Err(ParseError::InvalidIpv6)
} else {
if self.components_size < Self::IPV6_LEN {
if self.flags & flag::IS_IPV6_ZERO_SKIP == flag::IS_IPV6_ZERO_SKIP {
let zero_len = Self::IPV6_LEN.saturating_sub(self.components_size);
unsafe {
let components_ptr = self.components.as_mut_ptr();
ptr::copy(
components_ptr.add(self.zero_component_start as _),
components_ptr.add(self.zero_component_start.saturating_add(zero_len) as _),
self.components_size.saturating_sub(self.zero_component_start) as _);
ptr::write_bytes(components_ptr.add(self.zero_component_start as _), 0, zero_len as _);
}
} else {
return Err(ParseError::Ipv6InvalidComponentSize(self.components_size));
}
}
let ip = net::Ipv6Addr::new(
self.components[0], self.components[1],
self.components[2], self.components[3],
self.components[4], self.components[5],
self.components[6], self.components[7]
);
Ok(net::IpAddr::V6(ip))
}
}
#[inline(always)]
const fn on_digit(&mut self, pos: usize) -> Option<ParseError<'a>> {
match self.state {
ParserState::Digit => None,
ParserState::V6Sep if self.flags & flag::IS_IPV6_SEP_INITIAL == flag::IS_IPV6_SEP_INITIAL => Some(ParseError::InvalidIpv6),
_ => {
self.state = ParserState::Digit;
self.start_digit_position = pos;
None
}
}
}
#[inline(always)]
const fn on_v4_sep(&mut self, pos: usize) -> Option<ParseError<'a>> {
let result = match self.state {
ParserState::Digit => match self.family {
FamilyType::V6 => return Some(ParseError::InvalidIpv6),
FamilyType::Unknown => {
self.family = FamilyType::V4;
self.extract_v4_component(pos)
},
FamilyType::V4 => self.extract_v4_component(pos),
},
ParserState::V4Sep | ParserState::V6Sep | ParserState::Initial => Some(ParseError::InvalidIpv4),
};
self.state = ParserState::V4Sep;
result
}
#[inline(always)]
const fn on_v6_sep(&mut self, pos: usize) -> Option<ParseError<'a>> {
let result = match self.state {
ParserState::Digit => match self.family {
FamilyType::V4 => return Some(ParseError::InvalidIpv4),
FamilyType::Unknown => {
self.family = FamilyType::V6;
self.extract_v6_component(pos)
},
FamilyType::V6 => self.extract_v6_component(pos),
},
ParserState::V6Sep => {
if (self.flags & flag::IS_IPV6_ZERO_SKIP) == flag::IS_IPV6_ZERO_SKIP {
return Some(ParseError::Ipv6MultipleZeroAbbrv);
} else {
self.flags = (self.flags & !flag::IS_IPV6_SEP_INITIAL) | flag::IS_IPV6_ZERO_SKIP;
self.zero_component_start = self.components_size;
self.family = FamilyType::V6;
return None
}
},
ParserState::Initial => {
self.flags |= flag::IS_IPV6_SEP_INITIAL;
None
}
ParserState::V4Sep => Some(ParseError::InvalidIpv4),
};
self.state = ParserState::V6Sep;
result
}
const fn on_ip_end(&mut self, last_pos: usize) -> Result<net::IpAddr, ParseError<'a>> {
match self.state {
ParserState::Digit => self.read_ip_at_last(last_pos),
ParserState::V4Sep => Err(ParseError::InvalidIpv4),
ParserState::V6Sep if self.flags & flag::IS_IPV6_ZERO_SKIP == flag::IS_IPV6_ZERO_SKIP => {
if self.components_size == 0 {
Ok(net::IpAddr::V6(net::Ipv6Addr::UNSPECIFIED))
} else {
self.read_ipv6()
}
},
ParserState::V6Sep => Err(ParseError::InvalidIpv6),
ParserState::Initial => Err(ParseError::MissingIp),
}
}
const fn on_cidr_sep(&mut self, pos: usize) -> Result<u8, ParseError<'a>> {
let digit_pos = pos.saturating_add(1);
if digit_pos >= self.text.len() {
return Err(ParseError::MissingCidr);
}
let text = unsafe {
core::str::from_utf8_unchecked(
slice::from_raw_parts(self.text.as_ptr().add(digit_pos), self.text.len().saturating_sub(digit_pos))
)
};
match u8::from_str_radix(text, 10) {
Ok(result) => match self.family {
FamilyType::V4 => {
if result > crate::v4::BITS_LEN {
Err(ParseError::Ipv4CidrPrefixOverflow(result))
} else {
Ok(result)
}
},
FamilyType::V6 => {
if result > crate::v6::BITS_LEN {
Err(ParseError::Ipv6CidrPrefixOverflow(result))
} else {
Ok(result)
}
},
FamilyType::Unknown => Err(ParseError::InvalidCidr(text))
}
Err(_) => Err(ParseError::InvalidCidr(text)),
}
}
const fn parse(&mut self) -> Result<(net::IpAddr, Option<u8>), ParseError<'a>> {
let mut idx = 0;
while idx < self.text.len() {
let ch = self.text[idx];
if ch.is_ascii_hexdigit() {
if let Some(error) = self.on_digit(idx) {
return Err(error);
}
} else if ch == b'.' {
if let Some(error) = self.on_v4_sep(idx) {
return Err(error)
}
} else if ch == b':' {
if let Some(error) = self.on_v6_sep(idx) {
return Err(error)
}
} else if ch == b'/' {
let ip = match self.on_ip_end(idx) {
Ok(extracted_ip) => extracted_ip,
Err(error) => return Err(error),
};
match self.on_cidr_sep(idx) {
Ok(cidr) => return Ok((ip, Some(cidr))),
Err(error) => return Err(error),
}
} else if ch.is_ascii() {
return Err(ParseError::UnexpectedCharacter(ch as _, idx));
} else {
return Err(ParseError::UnexpectedCharacter(ch as _, idx));
}
idx = idx + 1;
}
match self.on_ip_end(idx) {
Ok(ip) => Ok((ip, None)),
Err(error) => Err(error)
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
InvalidComponent(&'a str),
InvalidCidr(&'a str),
UnexpectedCharacter(char, usize),
InvalidIp,
InvalidIpv4,
Ipv4InvalidComponentSize(u8),
InvalidIpv6,
Ipv6InvalidComponentSize(u8),
Ipv6MultipleZeroAbbrv,
NonAsciiCharacter(usize),
MissingIp,
MissingCidr,
Ipv4CidrPrefixOverflow(u8),
Ipv6CidrPrefixOverflow(u8),
}
impl fmt::Display for ParseError<'_> {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidIp => fmt.write_str("Input is not valid IP"),
Self::InvalidIpv4 => fmt.write_str("Address is not valid IPv4"),
Self::InvalidIpv6 => fmt.write_str("Address is not valid IPv6"),
Self::Ipv4InvalidComponentSize(size) => fmt.write_fmt(format_args!("IPv4 Address has '{size}' components but expected 4")),
Self::Ipv6InvalidComponentSize(size) => fmt.write_fmt(format_args!("IPv6 Address has '{size}' components but expected 8")),
Self::Ipv6MultipleZeroAbbrv => fmt.write_str("IPv6 contains more than 1 zero abbreviation"),
Self::UnexpectedCharacter(ch, pos) => fmt.write_fmt(format_args!("Encountered unexpected character '{ch}' at idx={pos}")),
Self::InvalidCidr(cidr) => {
fmt.write_str("Invalid Cidr prefix: ")?;
fmt.write_str(cidr)
},
Self::InvalidComponent(addr) => {
fmt.write_str("Invalid address component: ")?;
fmt.write_str(addr)
},
Self::NonAsciiCharacter(pos) => fmt.write_fmt(format_args!("Encountered non-ASCII character at idx={pos}")),
Self::MissingIp => fmt.write_str("Address is not specified"),
Self::MissingCidr => fmt.write_str("Prefix is not specified"),
Self::Ipv4CidrPrefixOverflow(prefix) => fmt.write_fmt(format_args!("Prefix '{prefix}' is greater than 32")),
Self::Ipv6CidrPrefixOverflow(prefix) => fmt.write_fmt(format_args!("Prefix '{prefix}' is greater than 128")),
}
}
}
impl core::error::Error for ParseError<'_> {
}
pub const fn parse_ip(text: &str) -> Result<(net::IpAddr, Option<u8>), ParseError<'_>> {
let text = text.as_bytes();
let mut parser = Parser {
state: ParserState::Initial,
flags: 0,
family: FamilyType::Unknown,
components_size: 0,
components: [0; 8],
zero_component_start: 0,
start_digit_position: 0,
text,
};
parser.parse()
}