use anyhow::Context;
#[inline]
pub fn ipv6_with_scope_id(input: &str) -> anyhow::Result<std::net::SocketAddr> {
if ip6_has_scope_id(input) {
let (addr, port) = parse_ip6_port(input)?;
let (addr, scope_id) = parse_ip6_scope_id(addr)?;
let mut socket =
<std::net::SocketAddrV6 as std::str::FromStr>::from_str(&format!("[{addr}]:{port}"))?;
socket.set_scope_id(crate::libc_abstraction::if_nametoindex(scope_id)?);
Ok(std::net::SocketAddr::V6(socket))
} else {
Ok(<std::net::SocketAddr as std::str::FromStr>::from_str(
input,
)?)
}
}
fn parse_ip6_port(input: &str) -> anyhow::Result<(&str, u16)> {
let (addr, port) = input
.rsplit_once(':')
.context("could not parse ip6 address")?;
Ok((
addr.strip_prefix('[')
.and_then(|s| s.strip_suffix(']'))
.ok_or_else(|| anyhow::anyhow!("ipv6 invalid format"))?,
port.parse::<u16>().context("could not parse port of ip6")?,
))
}
fn parse_ip6_scope_id(input: &str) -> anyhow::Result<(&str, &str)> {
input
.rsplit_once('%')
.context("could not parse ip6 address scope id")
}
fn ip6_has_scope_id(input: &str) -> bool {
input.rfind('%').is_some()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_ip6_with_scope_id() {
ipv6_with_scope_id("::1").unwrap_err();
ipv6_with_scope_id("::1%eth0").unwrap_err();
assert_eq!(
ipv6_with_scope_id("[::1]:25").unwrap(),
std::net::SocketAddr::new(std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), 25)
);
}
}