use std::net::IpAddr;
const ALPHABET: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
pub const GDNS_ZONE: &str = "g-dns.net";
pub fn base32_nopad(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(5) * 8);
let mut acc: u32 = 0;
let mut bits: u32 = 0;
for &b in data {
acc = (acc << 8) | b as u32;
bits += 8;
while bits >= 5 {
bits -= 5;
out.push(ALPHABET[((acc >> bits) & 0x1f) as usize] as char);
}
}
if bits > 0 {
out.push(ALPHABET[((acc << (5 - bits)) & 0x1f) as usize] as char);
}
out
}
pub fn gdns_host(ip: IpAddr) -> String {
let label = match ip {
IpAddr::V4(v4) => base32_nopad(&v4.octets()),
IpAddr::V6(v6) => base32_nopad(&v6.octets()),
};
format!("{label}.{GDNS_ZONE}")
}
fn host_only(authority: &str) -> &str {
if let Some(rest) = authority.strip_prefix('[') {
return rest.split(']').next().unwrap_or(rest);
}
match authority.rsplit_once(':') {
Some((h, p)) if !p.is_empty() && p.bytes().all(|c| c.is_ascii_digit()) => h,
_ => authority,
}
}
pub fn redirect_location(host_header: Option<&str>, local_ip: IpAddr, target: &str) -> String {
let authority = match host_header.map(host_only) {
Some(h) if !h.is_empty() && h.parse::<IpAddr>().is_err() => h.to_owned(),
_ => gdns_host(local_ip),
};
format!("https://{authority}{target}")
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn base32_known_vectors() {
assert_eq!(base32_nopad(&[0, 0, 0, 0]), "aaaaaaa");
assert_eq!(base32_nopad(&[255, 255, 255, 255]), "777777y");
assert_eq!(base32_nopad(&[0xff; 5]).len(), 8);
assert_eq!(base32_nopad(&[1, 2, 3, 4]).len(), 7);
assert_eq!(base32_nopad(&[0u8; 16]).len(), 26);
}
#[test]
fn gdns_host_suffix() {
let h = gdns_host(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
assert_eq!(h, "aaaaaaa.g-dns.net");
assert!(gdns_host(IpAddr::V6(Ipv6Addr::LOCALHOST)).ends_with(".g-dns.net"));
}
#[test]
fn host_only_strips_port() {
assert_eq!(host_only("example.com:8080"), "example.com");
assert_eq!(host_only("example.com"), "example.com");
assert_eq!(host_only("[::1]:443"), "::1");
assert_eq!(host_only("1.2.3.4:80"), "1.2.3.4");
}
#[test]
fn redirect_preserves_real_host() {
let ip = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 7));
assert_eq!(
redirect_location(Some("example.com"), ip, "/a?b=1"),
"https://example.com/a?b=1"
);
assert_eq!(
redirect_location(Some("example.com:8080"), ip, "/"),
"https://example.com/"
);
}
#[test]
fn redirect_ip_or_missing_goes_to_gdns() {
let ip = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 7));
let expect = format!("https://{}/p", gdns_host(ip));
assert_eq!(redirect_location(Some("203.0.113.7"), ip, "/p"), expect);
assert_eq!(redirect_location(None, ip, "/p"), expect);
}
}