1use core::fmt;
5#[cfg(feature = "std")]
6use lazy_regex::regex;
7
8use crate::error::Error;
9use crate::error::Result;
10
11#[cfg(feature = "std")]
12static HEX_RE: &lazy_regex::Lazy<lazy_regex::Regex> =
13 regex!(r"^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$");
14
15#[cfg(feature = "std")]
16static DOTTED_QUAD_RE: &lazy_regex::Lazy<lazy_regex::Regex> =
17 regex!(r"^([0-9a-fA-F]{0,4}:){2,6}(\d{1,3}\.){3}\d{1,3}$");
18
19#[allow(dead_code)]
21#[cfg(feature = "std")]
22static RE_RFC1924: &lazy_regex::Lazy<lazy_regex::Regex> =
23 regex!(r"^[0-9A-Za-z!#$%&()*+-;<=>?@^_`{|}~]{20}$");
24
25pub const MAX_IP: u128 = u128::MAX;
27
28pub const MIN_IP: u128 = 0;
30
31pub const RESERVED_RANGES: &[&str] = &[
33 UNSPECIFIED_ADDRESS,
34 LOOPBACK,
35 IPV4_MAPPED,
36 IPV6_TO_IPV4_NETWORK,
37 TEREDO_NETWORK,
38 PRIVATE_NETWORK,
39 LINK_LOCAL,
40 MULTICAST,
41 MULTICAST_LOOPBACK,
42 MULTICAST_LOCAL,
43 MULTICAST_SITE,
44 MULTICAST_SITE_ORG,
45 MULTICAST_GLOBAL,
46 MULTICAST_LOCAL_NODES,
47 MULTICAST_LOCAL_ROUTERS,
48 MULTICAST_LOCAL_DHCP,
49 MULTICAST_SITE_DHCP,
50];
51
52pub const UNSPECIFIED_ADDRESS: &str = "::/128";
55
56pub const LOOPBACK: &str = "::1/128";
59
60pub const LOCALHOST: &str = LOOPBACK;
63
64pub const IPV4_MAPPED: &str = "::ffff:0:0/96";
67
68pub const DOCUMENTATION_NETWORK: &str = "2001:db8::/32";
71
72pub const IPV6_TO_IPV4_NETWORK: &str = "2002::/16";
75
76pub const TEREDO_NETWORK: &str = "2001::/32";
79
80pub const PRIVATE_NETWORK: &str = "fd00::/8";
83
84pub const LINK_LOCAL: &str = "fe80::/10";
87
88pub const MULTICAST: &str = "ff00::/8";
91
92pub const MULTICAST_LOOPBACK: &str = "ff01::/16";
94
95pub const MULTICAST_LOCAL: &str = "ff02::/16";
97
98pub const MULTICAST_SITE: &str = "ff05::/16";
100
101pub const MULTICAST_SITE_ORG: &str = "ff08::/16";
103
104pub const MULTICAST_GLOBAL: &str = "ff0e::/16";
106
107pub const MULTICAST_LOCAL_NODES: &str = "ff02::1";
109
110pub const MULTICAST_LOCAL_ROUTERS: &str = "ff02::2";
112
113pub const MULTICAST_LOCAL_DHCP: &str = "ff02::1:2";
115
116pub const MULTICAST_SITE_DHCP: &str = "ff05::1:3";
118
119const RFC1924_ALPHABET_BYTES: &[u8; 85] =
120 b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
121
122const fn build_rfc1924_rev_table() -> [i8; 128] {
123 let mut table = [-1i8; 128];
124 let mut i = 0;
125 while i < RFC1924_ALPHABET_BYTES.len() {
126 let byte = RFC1924_ALPHABET_BYTES[i] as usize;
127 if byte < 128 {
128 table[byte] = i as i8;
129 }
130 i += 1;
131 }
132 table
133}
134
135const RFC1924_REV_TABLE: [i8; 128] = build_rfc1924_rev_table();
136
137#[inline]
138fn push_generated_ascii(out: &mut alloc::string::String, bytes: &[u8]) {
139 match core::str::from_utf8(bytes) {
140 Ok(text) => out.push_str(text),
141 Err(_) => push_generated_ascii_slow(out, bytes),
142 }
143}
144
145#[cold]
146fn push_generated_ascii_slow(out: &mut alloc::string::String, bytes: &[u8]) {
147 for &byte in bytes {
148 out.push(char::from(byte));
149 }
150}
151
152#[inline]
153fn generated_ascii_string(bytes: &[u8]) -> alloc::string::String {
154 match core::str::from_utf8(bytes) {
155 Ok(text) => alloc::string::String::from(text),
156 Err(_) => {
157 let mut out = alloc::string::String::with_capacity(bytes.len());
158 push_generated_ascii_slow(&mut out, bytes);
159 out
160 }
161 }
162}
163
164#[inline]
165fn generated_ascii_vec_string(bytes: alloc::vec::Vec<u8>) -> alloc::string::String {
166 match alloc::string::String::from_utf8(bytes) {
167 Ok(text) => text,
168 Err(err) => {
169 let bytes = err.into_bytes();
170 generated_ascii_string(&bytes)
171 }
172 }
173}
174
175#[inline]
176fn fmt_generated_ascii(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
177 match core::str::from_utf8(bytes) {
178 Ok(text) => f.write_str(text),
179 Err(_) => fmt_generated_ascii_slow(f, bytes),
180 }
181}
182
183#[cold]
184fn fmt_generated_ascii_slow(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
185 for &byte in bytes {
186 fmt::Write::write_char(f, char::from(byte))?;
187 }
188 Ok(())
189}
190
191enum Ipv6HextetParseError {
192 DottedQuadTail,
193 Invalid(Error),
194}
195
196#[cfg(feature = "std")]
209pub fn validate_ip_re(ip: &str) -> bool {
210 let is_hex = HEX_RE.is_match(ip);
211 let is_dotted_quad = DOTTED_QUAD_RE.is_match(ip);
212 (is_hex || is_dotted_quad) && ip2long(ip).is_ok()
213}
214
215pub fn validate_ip(ip: &str) -> bool {
238 ip2long(ip).is_ok()
239}
240
241#[inline(always)]
252pub fn ip2long(ip: &str) -> Result<u128> {
253 let bytes = ip.as_bytes();
254 match parse_ipv6_hextets(bytes) {
255 Ok(value) => Ok(value),
256 Err(Ipv6HextetParseError::DottedQuadTail) => parse_ipv6_mixed_dotted_quad(bytes),
257 Err(Ipv6HextetParseError::Invalid(err)) => Err(err),
258 }
259}
260
261#[inline(always)]
262fn parse_ipv6_mixed_dotted_quad(bytes: &[u8]) -> Result<u128> {
263 let Some(pos) = bytes.iter().rposition(|&b| b == b':') else {
264 return Err(Error::V6IP());
266 };
267
268 let suffix = &bytes[pos + 1..];
269 let (v6_src, v4_suffix) = if suffix.contains(&b'.') {
270 let src = if pos > 0 && bytes[pos - 1] == b':' {
271 &bytes[..pos + 1]
272 } else {
273 &bytes[..pos]
274 };
275 (src, Some(suffix)) } else {
277 (bytes, None)
279 };
280
281 let mut parts = [0u16; 8];
282 let mut capacity = 8;
283
284 if let Some(suffix) = v4_suffix {
285 if v6_src.is_empty() {
286 return Err(Error::V6IP());
287 }
288 let v4_int = parse_ipv4_dotted_quad_strict(suffix).ok_or(Error::V6IP())?;
291 parts[6] = (v4_int >> 16) as u16;
292 parts[7] = v4_int as u16;
293 capacity = 6;
294 }
295
296 parse_ipv6_prefix(v6_src, &mut parts, capacity)?;
297
298 Ok(groups_to_u128(&parts))
299}
300
301#[inline(always)]
302fn parse_ipv6_hextets(src: &[u8]) -> core::result::Result<u128, Ipv6HextetParseError> {
303 let len = src.len();
304 if len == 0 {
305 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
306 }
307
308 let mut idx = 0usize;
309 let mut head = 0u128;
310 let mut tail = 0u128;
311 let mut head_count = 0usize;
312 let mut tail_count = 0usize;
313 let mut compression_seen = false;
314
315 while idx < len {
316 if src[idx] == b':' {
317 if idx + 1 < len && src[idx + 1] == b':' && !compression_seen {
318 compression_seen = true;
319 idx += 2;
320 if idx == len {
321 break;
322 }
323 continue;
324 }
325 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
326 }
327
328 let mut value = 0u16;
329 let mut digits = 0usize;
330 while idx < len {
331 let b = src[idx];
332 if b == b':' {
333 break;
334 }
335 if b == b'.' {
336 return Err(Ipv6HextetParseError::DottedQuadTail);
337 }
338 let Some(nibble) = hex_nibble(b) else {
339 return Err(Ipv6HextetParseError::Invalid(Error::V6IPConvert()));
340 };
341 digits += 1;
342 if digits > 4 {
343 return Err(Ipv6HextetParseError::Invalid(Error::V6IPConvert()));
344 }
345 value = (value << 4) | u16::from(nibble);
346 idx += 1;
347 }
348
349 if digits == 0 {
350 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
351 }
352
353 if compression_seen {
354 if tail_count >= 8 {
355 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
356 }
357 tail = (tail << 16) | u128::from(value);
358 tail_count += 1;
359 } else {
360 if head_count >= 8 {
361 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
362 }
363 head = (head << 16) | u128::from(value);
364 head_count += 1;
365 }
366
367 if idx == len {
368 break;
369 }
370
371 if idx + 1 < len && src[idx + 1] == b':' {
372 if compression_seen {
373 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
374 }
375 compression_seen = true;
376 idx += 2;
377 if idx == len {
378 break;
379 }
380 } else {
381 idx += 1;
382 if idx == len {
383 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
384 }
385 }
386 }
387
388 if compression_seen {
389 if head_count + tail_count >= 8 {
390 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
391 }
392 let high = if head_count == 0 {
393 0
394 } else {
395 head << ((8 - head_count) * 16)
396 };
397 Ok(high | tail)
398 } else {
399 if head_count != 8 {
400 return Err(Ipv6HextetParseError::Invalid(Error::V6IP()));
401 }
402 Ok(head)
403 }
404}
405
406#[inline(always)]
408fn parse_ipv6_prefix(src: &[u8], parts: &mut [u16; 8], capacity: usize) -> Result<()> {
409 let mut i = 0;
410 let mut head_idx = 0;
411 let mut tail_idx = 0;
412 let mut tail_buf = [0u16; 7];
413 let mut compression_seen = false;
414
415 while i < src.len() {
417 if i + 1 < src.len() && src[i] == b':' && src[i + 1] == b':' {
419 compression_seen = true;
420 i += 2;
421 break;
422 }
423
424 let start = i;
426 while i < src.len() && src[i] != b':' {
427 i += 1;
428 }
429
430 if start == i {
431 return Err(Error::V6IP());
433 }
434
435 if head_idx >= capacity {
437 return Err(Error::V6IP());
438 }
439 parts[head_idx] = parse_hex_u16(&src[start..i]).ok_or(Error::V6IPConvert())?;
440 head_idx += 1;
441
442 if i < src.len() && src[i] == b':' {
443 i += 1; if i == src.len() {
445 return Err(Error::V6IP());
447 }
448 }
449
450 if i < src.len() && src[i] == b':' {
452 compression_seen = true;
453 i += 1;
454 break;
455 }
456 }
457
458 if compression_seen {
460 while i < src.len() {
461 let start = i;
462 while i < src.len() && src[i] != b':' {
463 i += 1;
464 }
465
466 if start == i {
467 return Err(Error::V6IP()); }
469
470 if tail_idx >= tail_buf.len() {
471 return Err(Error::V6IP());
472 }
473 tail_buf[tail_idx] = parse_hex_u16(&src[start..i]).ok_or(Error::V6IPConvert())?;
474 tail_idx += 1;
475
476 if i < src.len() && src[i] == b':' {
477 i += 1;
478 if i == src.len() {
479 return Err(Error::V6IP());
481 }
482 } else {
483 break;
484 }
485 }
486
487 if head_idx + tail_idx >= capacity {
489 return Err(Error::V6IP());
490 }
491 if tail_idx > 0 {
492 let insert_pos = capacity - tail_idx;
493 parts[insert_pos..capacity].copy_from_slice(&tail_buf[..tail_idx]);
494 }
495 } else if head_idx != capacity {
497 return Err(Error::V6IP());
499 }
500
501 Ok(())
502}
503
504#[inline(always)]
512fn parse_ipv4_dotted_quad_strict(src: &[u8]) -> Option<u32> {
513 let mut octets = [0u32; 4];
514 let mut octet_idx = 0usize;
515 let mut value = 0u32;
516 let mut digits = 0usize;
517
518 for &b in src {
519 match b {
520 b'0'..=b'9' => {
521 if digits == 0 {
522 value = (b - b'0') as u32;
523 digits = 1;
524 } else {
525 if digits == 1 && value == 0 {
526 return None;
528 }
529 value = value * 10 + u32::from(b - b'0');
530 digits += 1;
531 if digits > 3 || value > 255 {
532 return None;
533 }
534 }
535 }
536 b'.' => {
537 if digits == 0 || octet_idx >= 3 {
538 return None;
539 }
540 octets[octet_idx] = value;
541 octet_idx += 1;
542 value = 0;
543 digits = 0;
544 }
545 _ => return None,
546 }
547 }
548
549 if digits == 0 || octet_idx != 3 {
550 return None;
551 }
552 octets[3] = value;
553
554 Some((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3])
555}
556
557#[inline(always)]
559fn parse_hex_u16(src: &[u8]) -> Option<u16> {
560 let len = src.len();
561 if len == 0 || len > 4 {
562 return None;
563 }
564
565 let mut val = 0u16;
566 for &b in src {
567 val <<= 4;
568 val += match b {
569 b'0'..=b'9' => (b - b'0') as u16,
570 b'a'..=b'f' => (b - b'a' + 10) as u16,
571 b'A'..=b'F' => (b - b'A' + 10) as u16,
572 _ => return None,
573 };
574 }
575 Some(val)
576}
577
578#[inline(always)]
579fn hex_nibble(b: u8) -> Option<u8> {
580 match b {
581 b'0'..=b'9' => Some(b - b'0'),
582 b'a'..=b'f' => Some(b - b'a' + 10),
583 b'A'..=b'F' => Some(b - b'A' + 10),
584 _ => None,
585 }
586}
587
588#[inline(always)]
590fn groups_to_u128(groups: &[u16; 8]) -> u128 {
591 (u128::from(groups[0]) << 112)
592 | (u128::from(groups[1]) << 96)
593 | (u128::from(groups[2]) << 80)
594 | (u128::from(groups[3]) << 64)
595 | (u128::from(groups[4]) << 48)
596 | (u128::from(groups[5]) << 32)
597 | (u128::from(groups[6]) << 16)
598 | u128::from(groups[7])
599}
600
601pub fn long2ip(long_ip: u128, rfc1924: bool) -> alloc::string::String {
611 if rfc1924 {
612 return long2rfc1924(long_ip);
613 }
614
615 let mut buf = [0u8; 39];
616 let len = encode_long2ip(long_ip, &mut buf);
617 generated_ascii_vec_string(buf[..len].to_vec())
618}
619
620pub fn long2rfc1924(long_ip: u128) -> alloc::string::String {
631 let mut buf = [b'0'; 20];
632 let mut idx = 20;
633 let mut value = long_ip;
634
635 while value > 0 {
637 let digit = (value % 85) as usize;
638 value /= 85;
639 idx -= 1;
640 buf[idx] = RFC1924_ALPHABET_BYTES[digit];
641 }
642
643 generated_ascii_vec_string(buf.to_vec())
644}
645
646pub fn rfc19242long(s: &str) -> Option<u128> {
657 if s.len() != 20 {
658 return None;
659 }
660
661 let mut acc = 0u128;
662 for b in s.bytes() {
663 if b >= 128 {
664 return None;
665 }
666 let val = RFC1924_REV_TABLE[b as usize];
667 if val < 0 {
668 return None;
669 }
670 acc = acc.checked_mul(85)?.checked_add(val as u128)?;
671 }
672 Some(acc)
673}
674
675#[cfg(feature = "std")]
690pub fn validate_cidr_re(cidr: &str) -> bool {
691 let Some(slash_pos) = cidr.bytes().position(|b| b == b'/') else {
693 return false;
694 };
695
696 let ip_part = &cidr[..slash_pos];
697 let mask_bytes = &cidr.as_bytes()[slash_pos + 1..];
698
699 if mask_bytes.contains(&b'/') {
701 return false;
702 }
703
704 parse_prefix_0_128(mask_bytes).is_some() && validate_ip_re(ip_part)
706}
707
708pub fn validate_cidr(cidr: &str) -> bool {
723 let bytes = cidr.as_bytes();
724
725 let Some(slash_pos) = bytes.iter().position(|&b| b == b'/') else {
727 return false;
728 };
729
730 let ip_part = &cidr[..slash_pos];
731 let mask_start = slash_pos + 1;
732 let mask_bytes = &bytes[mask_start..];
733 if mask_bytes.contains(&b'/') {
734 return false;
735 }
736
737 parse_prefix_0_128(mask_bytes).is_some() && ip2long(ip_part).is_ok()
739}
740
741pub fn cidr2block(cidr: &str) -> Result<(alloc::string::String, alloc::string::String)> {
753 let (start, end) = cidr_bounds(cidr)?;
754 Ok((long2ip(start, false), long2ip(end, false)))
755}
756
757#[inline(always)]
770pub fn cidr_bounds(cidr: &str) -> Result<(u128, u128)> {
771 let Some(idx) = cidr.find('/') else {
772 return Err(Error::V6CIDR());
773 };
774
775 let ip_str = &cidr[..idx];
776 let prefix_bytes = &cidr.as_bytes()[idx + 1..];
777 if prefix_bytes.contains(&b'/') {
778 return Err(Error::V6CIDR());
779 }
780 let Some(prefix) = parse_prefix_0_128(prefix_bytes) else {
781 return Err(Error::V6CIDR());
782 };
783
784 let ip = ip2long(ip_str)?;
785 block_bounds(ip, prefix)
786}
787
788#[inline(always)]
798pub fn block_bounds(ip: u128, prefix: u8) -> Result<(u128, u128)> {
799 if prefix > 128 {
800 return Err(Error::V6CIDR());
801 }
802 Ok(block_from_ip_and_prefix_raw(ip, prefix))
803}
804
805fn block_from_ip_and_prefix_raw(ip: u128, prefix: u8) -> (u128, u128) {
806 let shift = 128 - u32::from(prefix);
807 if shift == 128 {
808 return (0, u128::MAX);
809 }
810 let block_start = ip & (u128::MAX << shift);
811 (block_start, block_start | ((1u128 << shift) - 1))
812}
813
814fn zero_run_bounds(hextets: &[u16; 8]) -> (usize, usize) {
815 let mut best = (8usize, 0usize);
816 let mut curr_start = 8usize;
817 let mut curr_len = 0usize;
818
819 for (i, &h) in hextets.iter().enumerate() {
820 if h == 0 {
821 if curr_start == 8 {
822 curr_start = i;
823 }
824 curr_len += 1;
825 } else {
826 if curr_len > best.1 {
827 best = (curr_start, curr_len);
828 }
829 curr_start = 8;
830 curr_len = 0;
831 }
832 }
833
834 if curr_len > best.1 {
835 best = (curr_start, curr_len);
836 }
837
838 if best.1 < 2 { (8, 0) } else { best }
839}
840
841fn write_hextet(buf: &mut [u8; 39], len: usize, val: u16) -> usize {
842 const HEX: &[u8; 16] = b"0123456789abcdef";
843
844 if val >= 0x1000 {
845 buf[len] = HEX[(val >> 12) as usize];
846 buf[len + 1] = HEX[((val >> 8) & 0xF) as usize];
847 buf[len + 2] = HEX[((val >> 4) & 0xF) as usize];
848 buf[len + 3] = HEX[(val & 0xF) as usize];
849 len + 4
850 } else if val >= 0x100 {
851 buf[len] = HEX[((val >> 8) & 0xF) as usize];
852 buf[len + 1] = HEX[((val >> 4) & 0xF) as usize];
853 buf[len + 2] = HEX[(val & 0xF) as usize];
854 len + 3
855 } else if val >= 0x10 {
856 buf[len] = HEX[((val >> 4) & 0xF) as usize];
857 buf[len + 1] = HEX[(val & 0xF) as usize];
858 len + 2
859 } else {
860 buf[len] = HEX[val as usize];
861 len + 1
862 }
863}
864
865fn encode_long2ip(long_ip: u128, buf: &mut [u8; 39]) -> usize {
866 let hextets = [
867 (long_ip >> 112) as u16,
868 (long_ip >> 96) as u16,
869 (long_ip >> 80) as u16,
870 (long_ip >> 64) as u16,
871 (long_ip >> 48) as u16,
872 (long_ip >> 32) as u16,
873 (long_ip >> 16) as u16,
874 long_ip as u16,
875 ];
876 let (best_start, best_len) = zero_run_bounds(&hextets);
877
878 let mut len = 0usize;
879 let mut i = 0usize;
880 while i < 8 {
881 if i == best_start {
882 buf[len] = b':';
883 buf[len + 1] = b':';
884 len += 2;
885 i += best_len;
886 continue;
887 }
888
889 if len > 0 && buf[len - 1] != b':' {
890 buf[len] = b':';
891 len += 1;
892 }
893
894 len = write_hextet(buf, len, hextets[i]);
895 i += 1;
896 }
897
898 len
899}
900
901pub(crate) fn push_long2ip(out: &mut alloc::string::String, long_ip: u128) {
902 let mut buf = [0u8; 39];
903 let len = encode_long2ip(long_ip, &mut buf);
904 push_generated_ascii(out, &buf[..len]);
905}
906
907pub(crate) fn fmt_long2ip(f: &mut fmt::Formatter<'_>, long_ip: u128) -> fmt::Result {
908 let mut buf = [0u8; 39];
909 let len = encode_long2ip(long_ip, &mut buf);
910 fmt_generated_ascii(f, &buf[..len])
911}
912
913#[inline(always)]
914fn parse_prefix_0_128(bytes: &[u8]) -> Option<u8> {
915 if bytes.is_empty() {
916 return None;
917 }
918 let mut value: u16 = 0;
919 for &b in bytes {
920 if !b.is_ascii_digit() {
921 return None;
922 }
923 value = value * 10 + u16::from(b - b'0');
924 if value > 128 {
925 return None;
926 }
927 }
928 Some(value as u8)
929}
930
931#[cfg(test)]
932mod tests;