#[allow(clippy::byte_char_slices)]
mod geo_data {
include!("geo_table.inc");
}
use geo_data::IPV4_COUNTRY;
mod geo_ipv6_data {
include!("geo_ipv6_table.inc");
}
use geo_ipv6_data::IPV6_COUNTRY;
pub fn ipv4_country(ip: [u8; 4]) -> [u8; 2] {
let prefix = ((ip[0] as u16) << 8) | ip[1] as u16;
match IPV4_COUNTRY.binary_search_by_key(&prefix, |e| e.0) {
Ok(i) => IPV4_COUNTRY[i].1,
Err(_) => [0, 0],
}
}
pub fn ipv6_country(ip: [u8; 16]) -> [u8; 2] {
lookup_ipv6(IPV6_COUNTRY, ip)
}
fn lookup_ipv6(table: &[([u8; 16], [u8; 16], [u8; 2])], ip: [u8; 16]) -> [u8; 2] {
let mut lo = 0usize;
let mut hi = table.len();
while lo < hi {
let mid = (lo + hi) / 2;
if table[mid].0 <= ip {
lo = mid + 1;
} else {
hi = mid;
}
}
if lo == 0 {
return [0, 0];
}
let (_, end, cc) = table[lo - 1];
if ip <= end {
cc
} else {
[0, 0]
}
}
pub fn ipv4_country_str(ip: [u8; 4]) -> alloc::string::String {
let c = ipv4_country(ip);
if c == [0, 0] {
alloc::string::String::new()
} else {
alloc::string::String::from_utf8_lossy(&c).into_owned()
}
}
pub fn ipv6_country_str(ip: [u8; 16]) -> alloc::string::String {
let c = ipv6_country(ip);
if c == [0, 0] {
alloc::string::String::new()
} else {
alloc::string::String::from_utf8_lossy(&c).into_owned()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_blocks_resolve() {
assert_eq!(ipv4_country([1, 3, 0, 1]), *b"CN");
assert_eq!(ipv4_country_str([1, 3, 0, 1]), "CN");
}
#[test]
fn private_ranges_map_to_unknown() {
assert_eq!(ipv4_country([10, 0, 0, 1]), [0, 0]);
assert_eq!(ipv4_country([192, 168, 1, 1]), [0, 0]);
assert_eq!(ipv4_country([127, 0, 0, 1]), [0, 0]);
assert_eq!(ipv4_country([169, 254, 1, 1]), [0, 0]);
assert_eq!(ipv4_country_str([10, 0, 0, 1]), "");
}
#[test]
fn table_is_sorted_for_binary_search() {
for w in IPV4_COUNTRY.windows(2) {
assert!(w[0].0 < w[1].0, "prefixes must be strictly sorted");
}
}
#[test]
fn ipv6_lookup_algorithm() {
let t: [([u8; 16], [u8; 16], [u8; 2]); 2] = [
(
[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[
0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff,
],
*b"ZZ",
),
(
[0x20, 0x01, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[
0x20, 0x01, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff,
],
*b"US",
),
];
assert_eq!(
lookup_ipv6(
&t,
[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
),
*b"ZZ"
);
assert_eq!(
lookup_ipv6(
&t,
[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff]
),
*b"ZZ"
);
assert_eq!(
lookup_ipv6(
&t,
[0x20, 0x01, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
),
*b"US"
);
assert_eq!(
lookup_ipv6(
&t,
[0x20, 0x01, 0x0d, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
),
[0, 0]
);
assert_eq!(
lookup_ipv6(&t, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
[0, 0]
);
assert_eq!(
lookup_ipv6(
&t,
[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff
]
),
[0, 0]
);
assert_eq!(
lookup_ipv6(&[], [0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
[0, 0]
);
}
#[test]
fn ipv6_is_unknown_until_a_dataset_exists() {
assert_eq!(
ipv6_country([0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
[0, 0]
);
assert_eq!(
ipv6_country_str([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
""
);
}
}