use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv6Addr};
pub const IP_EXACT_LIMIT: usize = 2;
pub fn canonicalize_ip(ip: IpAddr) -> IpAddr {
match ip {
IpAddr::V6(v6) => v6
.to_ipv4_mapped()
.map(IpAddr::V4)
.unwrap_or(IpAddr::V6(v6)),
other => other,
}
}
pub const fn ip_subnet_limit(k: usize) -> usize {
if k / 4 > 0 { k / 4 } else { 1 }
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct IPDiversityConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_per_ip: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_per_subnet: Option<usize>,
}
impl IPDiversityConfig {
#[must_use]
pub fn testnet() -> Self {
Self::permissive()
}
#[must_use]
pub fn permissive() -> Self {
Self {
max_per_ip: Some(usize::MAX),
max_per_subnet: Some(usize::MAX),
}
}
pub fn validate(&self) -> Result<()> {
if let Some(limit) = self.max_per_ip
&& limit < 1
{
anyhow::bail!("max_per_ip must be >= 1 (got {limit})");
}
if let Some(limit) = self.max_per_subnet
&& limit < 1
{
anyhow::bail!("max_per_subnet must be >= 1 (got {limit})");
}
Ok(())
}
}
#[allow(dead_code)]
pub trait GeoProvider: std::fmt::Debug {
fn lookup(&self, ip: Ipv6Addr) -> GeoInfo;
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GeoInfo {
pub asn: Option<u32>,
pub country: Option<String>,
pub is_hosting_provider: bool,
pub is_vpn_provider: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ip_diversity_config_default() {
let config = IPDiversityConfig::default();
assert!(config.max_per_ip.is_none());
assert!(config.max_per_subnet.is_none());
}
#[test]
fn test_canonicalize_ipv4_mapped() {
let mapped: IpAddr = "::ffff:10.0.0.1".parse().unwrap();
let canonical = canonicalize_ip(mapped);
let expected: IpAddr = "10.0.0.1".parse().unwrap();
assert_eq!(canonical, expected);
}
#[test]
fn test_canonicalize_native_ipv6_unchanged() {
let v6: IpAddr = "2001:db8::1".parse().unwrap();
assert_eq!(canonicalize_ip(v6), v6);
}
#[test]
fn test_ip_subnet_limit() {
assert_eq!(ip_subnet_limit(20), 5);
assert_eq!(ip_subnet_limit(8), 2);
assert_eq!(ip_subnet_limit(1), 1);
assert_eq!(ip_subnet_limit(0), 1);
}
}