use lazy_regex::regex;
use once_cell::sync::Lazy;
use tinyvec::ArrayVec;
use crate::error::Error;
use crate::error::Result;
static HEX_RE: &lazy_regex::Lazy<lazy_regex::Regex> =
regex!(r"^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$");
static DOTTED_QUAD_RE: &lazy_regex::Lazy<lazy_regex::Regex> =
regex!(r"^([0-9a-f]{0,4}:){2,6}(\d{1,3}\.){0,3}\d{1,3}$");
#[allow(dead_code)]
static RE_RFC1924: &lazy_regex::Lazy<lazy_regex::Regex> =
regex!(r"^[0-9A-Za-z!#$%&()*+-;<=>?@^_`{|}~]{20}$");
pub const MAX_IP: u128 = u128::MAX;
pub const MIN_IP: u128 = 0;
pub static RESERVED_RANGES: Lazy<alloc::vec::Vec<&str>> = Lazy::new(|| {
alloc::vec![
UNSPECIFIED_ADDRESS,
LOOPBACK,
IPV4_MAPPED,
IPV6_TO_IPV4_NETWORK,
TEREDO_NETWORK,
PRIVATE_NETWORK,
LINK_LOCAL,
MULTICAST,
MULTICAST_LOOPBACK,
MULTICAST_LOCAL,
MULTICAST_SITE,
MULTICAST_SITE_ORG,
MULTICAST_GLOBAL,
MULTICAST_LOCAL_NODES,
MULTICAST_LOCAL_ROUTERS,
MULTICAST_LOCAL_DHCP,
MULTICAST_SITE_DHCP,
]
});
pub const UNSPECIFIED_ADDRESS: &str = "::/128";
pub const LOOPBACK: &str = "::1/128";
pub const LOCALHOST: &str = LOOPBACK;
pub const IPV4_MAPPED: &str = "::ffff:0:0/96";
pub const DOCUMENTATION_NETWORK: &str = "2001::db8::/32";
pub const IPV6_TO_IPV4_NETWORK: &str = "2002::/16";
pub const TEREDO_NETWORK: &str = "2001::/32";
pub const PRIVATE_NETWORK: &str = "fd00::/8";
pub const LINK_LOCAL: &str = "fe80::/10";
pub const MULTICAST: &str = "ff00::/8";
pub const MULTICAST_LOOPBACK: &str = "ff01::/16";
pub const MULTICAST_LOCAL: &str = "ff02::/16";
pub const MULTICAST_SITE: &str = "ff05::/16";
pub const MULTICAST_SITE_ORG: &str = "ff08::/16";
pub const MULTICAST_GLOBAL: &str = "ff0e::/16";
pub const MULTICAST_LOCAL_NODES: &str = "ff02::1";
pub const MULTICAST_LOCAL_ROUTERS: &str = "ff02::2";
pub const MULTICAST_LOCAL_DHCP: &str = "ff02::1:2";
pub const MULTICAST_SITE_DHCP: &str = "ff05::1:3";
const RFC1924_ALPHABET_BYTES: &[u8; 85] =
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
const fn build_rfc1924_rev_table() -> [i8; 128] {
let mut table = [-1i8; 128];
let mut i = 0;
while i < RFC1924_ALPHABET_BYTES.len() {
let byte = RFC1924_ALPHABET_BYTES[i] as usize;
if byte < 128 {
table[byte] = i as i8;
}
i += 1;
}
table
}
const RFC1924_REV_TABLE: [i8; 128] = build_rfc1924_rev_table();
pub fn validate_ip_re(ip: &str) -> bool {
let is_hex = HEX_RE.is_match(ip);
let is_dotted_quad = DOTTED_QUAD_RE.is_match(ip);
if is_hex {
ip.split("::").count() <= 2
} else if is_dotted_quad {
if ip.split("::").count() > 2 {
return false;
}
let quads: ArrayVec<[&str; 4]> =
ip.split(':').next_back().map_or(ArrayVec::new(), |last| {
last.split('.').collect::<ArrayVec<[&str; 4]>>()
});
quads
.iter()
.all(|q| q.parse::<i32>().ok().is_some_and(|q| q <= 255))
} else {
false
}
}
pub fn validate_ip(ip: &str) -> bool {
ip2long(ip).is_ok()
}
pub fn ip2long(ip: &str) -> Result<u128> {
let bytes = ip.as_bytes();
let (v6_src, v4_suffix) = match bytes.iter().rposition(|&b| b == b':') {
Some(pos) => {
let suffix = &bytes[pos + 1..];
if suffix.contains(&b'.') {
let src = if pos > 0 && bytes[pos - 1] == b':' {
&bytes[..pos + 1]
} else {
&bytes[..pos]
};
(src, Some(suffix)) } else {
(bytes, None) }
}
None if bytes.contains(&b'.') => (&[][..], Some(bytes)), None => (bytes, None), };
let mut parts = [0u16; 8];
let mut capacity = 8;
if let Some(suffix) = v4_suffix {
let v4_str = core::str::from_utf8(suffix).map_err(|_| Error::V6IP())?;
let v4_int = crate::ipv4::ip2long(v4_str)?;
parts[6] = (v4_int >> 16) as u16;
parts[7] = v4_int as u16;
capacity = 6;
}
if !v6_src.is_empty() {
parse_ipv6_prefix(v6_src, &mut parts, capacity)?;
} else if capacity == 8 && v4_suffix.is_none() {
if bytes != b"::" {
return Err(Error::V6IP());
}
}
Ok(groups_to_u128(&parts))
}
#[inline(always)]
fn parse_ipv6_prefix(src: &[u8], parts: &mut [u16; 8], capacity: usize) -> Result<()> {
let mut i = 0;
let mut head_idx = 0;
let mut tail_idx = 0;
let mut tail_buf = [0u16; 7];
let mut compression_seen = false;
while i < src.len() {
if i + 1 < src.len() && src[i] == b':' && src[i + 1] == b':' {
if compression_seen {
return Err(Error::V6IP()); }
compression_seen = true;
i += 2;
break;
}
let start = i;
while i < src.len() && src[i] != b':' {
i += 1;
}
if start != i {
if head_idx >= capacity {
return Err(Error::V6IP());
}
parts[head_idx] = parse_hex_u16(&src[start..i]).ok_or(Error::V6IPConvert())?;
head_idx += 1;
}
if i < src.len() && src[i] == b':' {
i += 1; }
if i < src.len() && src[i] == b':' {
if compression_seen {
return Err(Error::V6IP()); }
compression_seen = true;
i += 1;
break;
}
}
if compression_seen {
while i < src.len() {
let start = i;
while i < src.len() && src[i] != b':' {
i += 1;
}
if start == i {
return Err(Error::V6IP()); }
if tail_idx >= tail_buf.len() {
return Err(Error::V6IP());
}
tail_buf[tail_idx] = parse_hex_u16(&src[start..i]).ok_or(Error::V6IPConvert())?;
tail_idx += 1;
if i < src.len() && src[i] == b':' {
i += 1;
} else {
break;
}
}
if head_idx + tail_idx > capacity {
return Err(Error::V6IP());
}
if tail_idx > 0 {
let insert_pos = capacity - tail_idx;
parts[insert_pos..capacity].copy_from_slice(&tail_buf[..tail_idx]);
}
} else if head_idx != capacity {
return Err(Error::V6IP());
}
Ok(())
}
#[inline(always)]
fn parse_hex_u16(src: &[u8]) -> Option<u16> {
let len = src.len();
if len == 0 || len > 4 {
return None;
}
let mut val = 0u16;
for &b in src {
val <<= 4;
val += match b {
b'0'..=b'9' => (b - b'0') as u16,
b'a'..=b'f' => (b - b'a' + 10) as u16,
b'A'..=b'F' => (b - b'A' + 10) as u16,
_ => return None,
};
}
Some(val)
}
#[inline(always)]
fn groups_to_u128(groups: &[u16; 8]) -> u128 {
groups
.iter()
.fold(0u128, |acc, &g| (acc << 16) | u128::from(g))
}
pub fn long2ip(long_ip: u128, rfc1924: bool) -> alloc::string::String {
if rfc1924 {
return long2rfc1924(long_ip);
}
let hextets = [
(long_ip >> 112) as u16,
(long_ip >> 96) as u16,
(long_ip >> 80) as u16,
(long_ip >> 64) as u16,
(long_ip >> 48) as u16,
(long_ip >> 32) as u16,
(long_ip >> 16) as u16,
long_ip as u16,
];
let (best_start, best_len) = {
let mut best = (8usize, 0usize); let mut curr_start = 8usize;
let mut curr_len = 0usize;
for (i, &h) in hextets.iter().enumerate() {
if h == 0 {
if curr_start == 8 {
curr_start = i;
}
curr_len += 1;
} else {
if curr_len > best.1 {
best = (curr_start, curr_len);
}
curr_start = 8;
curr_len = 0;
}
}
if curr_len > best.1 {
best = (curr_start, curr_len);
}
if best.1 < 2 {
(8, 0)
} else {
best
}
};
let mut buf = alloc::vec::Vec::with_capacity(39);
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut i = 0;
while i < 8 {
if i == best_start {
buf.extend_from_slice(b"::");
i = best_start + best_len;
continue;
}
if let Some(&last) = buf.last() {
if last != b':' {
buf.push(b':');
}
}
let val = hextets[i];
if val >= 0x1000 {
buf.push(HEX[(val >> 12) as usize]);
buf.push(HEX[((val >> 8) & 0xF) as usize]);
buf.push(HEX[((val >> 4) & 0xF) as usize]);
buf.push(HEX[(val & 0xF) as usize]);
} else if val >= 0x100 {
buf.push(HEX[((val >> 8) & 0xF) as usize]);
buf.push(HEX[((val >> 4) & 0xF) as usize]);
buf.push(HEX[(val & 0xF) as usize]);
} else if val >= 0x10 {
buf.push(HEX[((val >> 4) & 0xF) as usize]);
buf.push(HEX[(val & 0xF) as usize]);
} else {
buf.push(HEX[val as usize]);
}
i += 1;
}
alloc::string::String::from_utf8(buf).unwrap()
}
pub fn long2rfc1924(long_ip: u128) -> alloc::string::String {
let mut buf = [b'0'; 20];
let mut idx = 20;
let mut value = long_ip;
while value > 0 {
let digit = (value % 85) as usize;
value /= 85;
idx -= 1;
buf[idx] = RFC1924_ALPHABET_BYTES[digit];
}
alloc::string::String::from_utf8(buf.to_vec()).expect("alphabet is valid ASCII")
}
pub fn rfc19242long(s: &str) -> Option<u128> {
if s.len() != 20 {
return None;
}
let mut acc = 0u128;
for b in s.bytes() {
if b >= 128 {
return None;
}
let val = RFC1924_REV_TABLE[b as usize];
if val < 0 {
return None;
}
acc = acc.checked_mul(85)?.checked_add(val as u128)?;
}
Some(acc)
}
pub fn validate_cidr_re(cidr: &str) -> bool {
let Some(slash_pos) = cidr.bytes().position(|b| b == b'/') else {
return false;
};
let ip_part = &cidr[..slash_pos];
let mask_bytes = &cidr.as_bytes()[slash_pos + 1..];
if mask_bytes.is_empty() || mask_bytes.len() > 3 {
return false;
}
let mut prefix: u16 = 0;
for &b in mask_bytes {
if !b.is_ascii_digit() {
return false;
}
prefix = prefix * 10 + (b - b'0') as u16;
}
prefix <= 128 && validate_ip_re(ip_part)
}
pub fn validate_cidr(cidr: &str) -> bool {
let bytes = cidr.as_bytes();
let len = bytes.len();
let Some(slash_pos) = bytes.iter().position(|&b| b == b'/') else {
return false;
};
let ip_part = &cidr[..slash_pos];
let mask_start = slash_pos + 1;
let mask_len = len.saturating_sub(mask_start);
if mask_len == 0 || mask_len > 3 {
return false;
}
let mut prefix: u16 = 0;
for &b in &bytes[mask_start..] {
if !b.is_ascii_digit() {
return false;
}
prefix = prefix * 10 + (b - b'0') as u16;
if prefix > 128 {
return false;
}
}
ip2long(ip_part).is_ok()
}
pub fn cidr2block(cidr: &str) -> Result<(alloc::string::String, alloc::string::String)> {
if let Some(idx) = cidr.find('/') {
let ip_str = &cidr[..idx];
let prefix_str = &cidr[idx + 1..];
if let Ok(prefix) = prefix_str.parse::<u128>() {
if prefix <= 128 {
if let Ok(ip) = ip2long(ip_str) {
let shift: u32 = 128 - prefix as u32;
let block_start: u128 = ip
.checked_shr(shift)
.unwrap_or(0)
.checked_shl(shift)
.unwrap_or(0);
let mut mask = u128::MAX;
if let Some(shift) = 1u128.checked_shl(shift) {
if let Some(sub) = shift.checked_sub(1) {
mask = sub;
}
}
let block_end = block_start | mask;
return Ok((long2ip(block_start, false), long2ip(block_end, false)));
}
}
}
}
Err(Error::V6CIDR())
}
#[cfg(test)]
mod tests;