use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use crate::error::{Error, Result};
pub fn lookup_host(host: &str, port: u16) -> Result<Vec<SocketAddr>> {
let lower = host.to_ascii_lowercase();
if let Some(encoded) = lower.strip_suffix(".g-dns.net") {
let mut out = Vec::new();
for part in encoded.split('-') {
if part.is_empty() {
continue;
}
if let Some(ip) = decode_base32_ip(part) {
out.push(SocketAddr::new(ip, port));
}
}
if !out.is_empty() {
return Ok(out);
}
}
Ok((host, port).to_socket_addrs().map_err(Error::Io)?.collect())
}
fn decode_base32_ip(encoded: &str) -> Option<IpAddr> {
let data = base32_decode(encoded)?;
match data.len() {
4 => Some(IpAddr::V4(Ipv4Addr::new(
data[0], data[1], data[2], data[3],
))),
16 => {
let mut b = [0u8; 16];
b.copy_from_slice(&data);
Some(IpAddr::V6(Ipv6Addr::from(b)))
}
_ => None,
}
}
fn base32_decode(s: &str) -> Option<Vec<u8>> {
let mut bits: u32 = 0;
let mut nbits: u32 = 0;
let mut out = Vec::with_capacity(s.len() * 5 / 8);
for c in s.bytes() {
let v = match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a',
b'2'..=b'7' => c - b'2' + 26,
_ => return None,
};
bits = (bits << 5) | v as u32;
nbits += 5;
if nbits >= 8 {
nbits -= 8;
out.push((bits >> nbits) as u8);
}
}
if bits & ((1 << nbits) - 1) != 0 {
return None;
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base32_ipv4() {
assert_eq!(
decode_base32_ip("P4AAAAI"),
Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
);
assert_eq!(
decode_base32_ip("p4aaaai"),
Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
);
}
#[test]
fn base32_ipv6() {
let mut b = [0u8; 16];
b[15] = 1;
let enc = encode_base32(&b);
assert_eq!(decode_base32_ip(&enc), Some(IpAddr::V6(Ipv6Addr::from(b))));
}
#[test]
fn rejects_garbage() {
assert_eq!(decode_base32_ip("191"), None); assert_eq!(decode_base32_ip("AAAA"), None); }
fn encode_base32(data: &[u8]) -> String {
const ALPHA: &[u8; 32] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let mut bits: u64 = 0;
let mut nbits: u32 = 0;
let mut out = String::new();
for &b in data {
bits = (bits << 8) | b as u64;
nbits += 8;
while nbits >= 5 {
nbits -= 5;
out.push(ALPHA[(bits >> nbits) as usize & 31] as char);
}
}
if nbits > 0 {
out.push(ALPHA[(bits << (5 - nbits)) as usize & 31] as char);
}
out
}
#[test]
fn lookup_gdns() {
let addrs = lookup_host("P4AAAAI.g-dns.net", 443).unwrap();
assert_eq!(addrs, vec![SocketAddr::from(([127, 0, 0, 1], 443))]);
}
}