use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AddrError {
#[error("invalid address: {0}")]
InvalidAddress(String),
#[error("invalid prefix length: {0}")]
InvalidPrefix(String),
#[error("invalid MAC address: {0}")]
InvalidMac(String),
}
pub type Result<T> = std::result::Result<T, AddrError>;
pub fn parse_addr(s: &str) -> Result<IpAddr> {
s.parse()
.map_err(|_| AddrError::InvalidAddress(s.to_string()))
}
pub fn parse_prefix(s: &str) -> Result<(IpAddr, u8)> {
if let Some((addr_str, prefix_str)) = s.split_once('/') {
let addr = parse_addr(addr_str)?;
let prefix: u8 = prefix_str
.parse()
.map_err(|_| AddrError::InvalidPrefix(prefix_str.to_string()))?;
let max_prefix = if addr.is_ipv4() { 32 } else { 128 };
if prefix > max_prefix {
return Err(AddrError::InvalidPrefix(format!(
"{} exceeds maximum {} for address family",
prefix, max_prefix
)));
}
Ok((addr, prefix))
} else {
let addr = parse_addr(s)?;
let prefix = if addr.is_ipv4() { 32 } else { 128 };
Ok((addr, prefix))
}
}
pub fn format_addr(addr: &IpAddr) -> String {
addr.to_string()
}
pub fn format_prefix(addr: &IpAddr, prefix: u8) -> String {
format!("{}/{}", addr, prefix)
}
pub fn format_ipv4(bytes: &[u8]) -> Option<String> {
if bytes.len() >= 4 {
Some(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).to_string())
} else {
None
}
}
pub fn format_ipv6(bytes: &[u8]) -> Option<String> {
if bytes.len() >= 16 {
let octets: [u8; 16] = bytes[..16].try_into().ok()?;
Some(Ipv6Addr::from(octets).to_string())
} else {
None
}
}
pub fn format_addr_bytes(bytes: &[u8], family: u8) -> Option<String> {
match family {
2 => format_ipv4(bytes), 10 => format_ipv6(bytes), _ => None,
}
}
pub fn parse_mac(s: &str) -> Result<[u8; 6]> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 6 {
return Err(AddrError::InvalidMac(s.to_string()));
}
let mut mac = [0u8; 6];
for (i, part) in parts.iter().enumerate() {
mac[i] = u8::from_str_radix(part, 16).map_err(|_| AddrError::InvalidMac(s.to_string()))?;
}
Ok(mac)
}
pub fn format_mac(bytes: &[u8]) -> String {
if bytes.len() >= 6 {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
)
} else {
bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(":")
}
}
pub fn ipv4_in_prefix(addr: Ipv4Addr, prefix_addr: Ipv4Addr, prefix_len: u8) -> bool {
if prefix_len == 0 {
return true;
}
if prefix_len > 32 {
return false;
}
let mask = !0u32 << (32 - prefix_len);
let addr_bits = u32::from(addr);
let prefix_bits = u32::from(prefix_addr);
(addr_bits & mask) == (prefix_bits & mask)
}
pub fn ipv6_in_prefix(addr: Ipv6Addr, prefix_addr: Ipv6Addr, prefix_len: u8) -> bool {
if prefix_len == 0 {
return true;
}
if prefix_len > 128 {
return false;
}
let addr_bits = u128::from(addr);
let prefix_bits = u128::from(prefix_addr);
let mask = !0u128 << (128 - prefix_len);
(addr_bits & mask) == (prefix_bits & mask)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_addr() {
assert_eq!(
parse_addr("192.168.1.1").unwrap(),
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))
);
assert_eq!(parse_addr("::1").unwrap(), IpAddr::V6(Ipv6Addr::LOCALHOST));
}
#[test]
fn test_parse_prefix() {
let (addr, prefix) = parse_prefix("192.168.1.0/24").unwrap();
assert_eq!(addr, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)));
assert_eq!(prefix, 24);
}
#[test]
fn test_parse_mac() {
let mac = parse_mac("aa:bb:cc:dd:ee:ff").unwrap();
assert_eq!(mac, [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
}
#[test]
fn test_format_mac() {
assert_eq!(
format_mac(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
"aa:bb:cc:dd:ee:ff"
);
}
}