rustauth_core/utils/
ip.rs1use std::net::{IpAddr, Ipv6Addr};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Ipv6Subnet {
6 Prefix32,
7 Prefix48,
8 Prefix64,
9 Full,
10}
11
12impl Ipv6Subnet {
13 const fn bits(self) -> u8 {
14 match self {
15 Self::Prefix32 => 32,
16 Self::Prefix48 => 48,
17 Self::Prefix64 => 64,
18 Self::Full => 128,
19 }
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct NormalizeIpOptions {
26 pub ipv6_subnet: Ipv6Subnet,
27}
28
29impl Default for NormalizeIpOptions {
30 fn default() -> Self {
31 Self {
32 ipv6_subnet: Ipv6Subnet::Prefix64,
33 }
34 }
35}
36
37pub fn is_valid_ip(ip: &str) -> bool {
39 ip.parse::<IpAddr>().is_ok()
40}
41
42pub fn normalize_ip(ip: &str) -> String {
44 normalize_ip_with_options(ip, NormalizeIpOptions::default())
45}
46
47pub fn normalize_ip_with_options(ip: &str, options: NormalizeIpOptions) -> String {
49 match ip.parse::<IpAddr>() {
50 Ok(IpAddr::V4(ip)) => ip.to_string(),
51 Ok(IpAddr::V6(ip)) => normalize_ipv6(ip, options.ipv6_subnet),
52 Err(_) => ip.to_ascii_lowercase(),
53 }
54}
55
56pub fn create_rate_limit_key(ip: &str, path: &str) -> String {
58 format!("{ip}|{path}")
59}
60
61pub fn create_rate_limit_key_with_suffix(ip: &str, path: &str, suffix: &str) -> String {
66 format!("{}|{}", create_rate_limit_key(ip, path), suffix)
67}
68
69fn normalize_ipv6(ip: Ipv6Addr, subnet: Ipv6Subnet) -> String {
70 if let Some(mapped) = ip.to_ipv4_mapped() {
71 return mapped.to_string();
72 }
73
74 format_ipv6_segments(mask_ipv6_segments(ip.segments(), subnet.bits()))
75}
76
77fn mask_ipv6_segments(mut segments: [u16; 8], prefix_bits: u8) -> [u16; 8] {
78 let mut bits_remaining = prefix_bits;
79
80 for segment in &mut segments {
81 if bits_remaining >= 16 {
82 bits_remaining -= 16;
83 continue;
84 }
85
86 if bits_remaining == 0 {
87 *segment = 0;
88 continue;
89 }
90
91 let mask = u16::MAX << (16 - bits_remaining);
92 *segment &= mask;
93 bits_remaining = 0;
94 }
95
96 segments
97}
98
99fn format_ipv6_segments(segments: [u16; 8]) -> String {
100 segments
101 .iter()
102 .map(|segment| format!("{segment:04x}"))
103 .collect::<Vec<_>>()
104 .join(":")
105}