1use core::cmp::Ordering;
2use core::fmt;
3use core::str::FromStr;
4
5use super::delegate;
6use crate::{
7 concrete::{self, Ipv4, Ipv6},
8 error::Error,
9 traits::{
10 self,
11 primitive::{Address as _, IntoIpv6Segments as _},
12 Afi,
13 },
14};
15
16#[allow(variant_size_differences)]
37#[derive(Clone, Copy, Hash, PartialEq, Eq)]
38pub enum Address {
39 Ipv4(concrete::Address<Ipv4>),
41 Ipv6(concrete::Address<Ipv6>),
43}
44
45impl Address {
46 #[must_use]
61 pub const fn is_ipv4(&self) -> bool {
62 matches!(self, Self::Ipv4(_))
63 }
64
65 #[must_use]
80 pub const fn is_ipv6(&self) -> bool {
81 matches!(self, Self::Ipv6(_))
82 }
83
84 #[allow(clippy::wrong_self_convention)]
105 #[must_use]
106 pub fn to_canonical(&self) -> Self {
107 match self {
108 Self::Ipv4(_) => *self,
109 Self::Ipv6(ipv6_addr) => ipv6_addr.to_canonical(),
110 }
111 }
112}
113
114impl traits::Address for Address {
115 delegate! {
116 fn afi(&self) -> concrete::Afi;
117 fn is_broadcast(&self) -> bool;
118 fn is_link_local(&self) -> bool;
119 fn is_private(&self) -> bool;
120 fn is_reserved(&self) -> bool;
121 fn is_shared(&self) -> bool;
122 fn is_thisnet(&self) -> bool;
123 fn is_benchmarking(&self) -> bool;
124 fn is_documentation(&self) -> bool;
125 fn is_global(&self) -> bool;
126 fn is_loopback(&self) -> bool;
127 fn is_multicast(&self) -> bool;
128 fn is_unicast(&self) -> bool;
129 fn is_unspecified(&self) -> bool;
130 fn is_unique_local(&self) -> bool;
131 }
132}
133
134macro_rules! impl_from_address {
135 ( $( $af:ident ),* $(,)? ) => {
136 $(
137 impl From<concrete::Address<$af>> for Address {
138 fn from(addr: concrete::Address<$af>) -> Self {
139 Self::$af(addr)
140 }
141 }
142 )*
143 }
144}
145impl_from_address!(Ipv4, Ipv6);
146
147macro_rules! impl_from_primitive {
148 ( $( $af:ident ),* $(,)? ) => {
149 $(
150 impl From<<$af as Afi>::Primitive> for Address {
151 fn from(primitive: <$af as Afi>::Primitive) -> Self {
152 concrete::Address::<$af>::new(primitive).into()
153 }
154 }
155 )*
156 }
157}
158impl_from_primitive!(Ipv4, Ipv6);
159
160macro_rules! impl_from_octets {
161 ( $( $af:ident ),* $(,)? ) => {
162 $(
163 impl From<<$af as Afi>::Octets> for Address {
164 fn from(octets: <$af as Afi>::Octets) -> Self {
165 <$af as Afi>::Primitive::from_be_bytes(octets).into()
166 }
167 }
168 )*
169 }
170}
171impl_from_octets!(Ipv4, Ipv6);
172
173impl From<[u16; 8]> for Address {
174 fn from(segments: [u16; 8]) -> Self {
175 <Ipv6 as Afi>::Primitive::from_segments(segments).into()
176 }
177}
178
179#[cfg(feature = "std")]
180impl From<std::net::Ipv4Addr> for Address {
181 fn from(addr: std::net::Ipv4Addr) -> Self {
182 concrete::Address::from(addr).into()
183 }
184}
185
186#[cfg(feature = "std")]
187impl From<std::net::Ipv6Addr> for Address {
188 fn from(addr: std::net::Ipv6Addr) -> Self {
189 concrete::Address::from(addr).into()
190 }
191}
192
193#[cfg(feature = "std")]
194impl From<std::net::IpAddr> for Address {
195 fn from(addr: std::net::IpAddr) -> Self {
196 match addr {
197 std::net::IpAddr::V4(addr) => addr.into(),
198 std::net::IpAddr::V6(addr) => addr.into(),
199 }
200 }
201}
202
203impl PartialOrd for Address {
204 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
205 match (self, other) {
206 (Self::Ipv4(addr), Self::Ipv4(other)) => addr.partial_cmp(other),
207 (Self::Ipv6(addr), Self::Ipv6(other)) => addr.partial_cmp(other),
208 _ => None,
209 }
210 }
211}
212
213macro_rules! impl_partial_cmp {
214 ( $( $af:ident ),* $(,)? ) => {
215 $(
216 impl PartialEq<concrete::Address<$af>> for Address {
217 fn eq(&self, other: &concrete::Address<$af>) -> bool {
218 if let Self::$af(addr) = self {
219 addr.eq(other)
220 } else {
221 false
222 }
223 }
224 }
225
226 impl PartialEq<Address> for concrete::Address<$af> {
227 fn eq(&self, other: &Address) -> bool {
228 other.eq(self)
229 }
230 }
231
232 impl PartialOrd<concrete::Address<$af>> for Address {
233 fn partial_cmp(&self, other: &concrete::Address<$af>) -> Option<Ordering> {
234 if let Self::$af(addr) = self {
235 addr.partial_cmp(other)
236 } else {
237 None
238 }
239 }
240 }
241
242 impl PartialOrd<Address> for concrete::Address<$af> {
243 fn partial_cmp(&self, other: &Address) -> Option<Ordering> {
244 other.partial_cmp(self).map(Ordering::reverse)
245 }
246 }
247 )*
248 }
249}
250impl_partial_cmp!(Ipv4, Ipv6);
251
252impl FromStr for Address {
253 type Err = Error;
254
255 fn from_str(s: &str) -> Result<Self, Self::Err> {
256 <Ipv4 as Afi>::Primitive::parse_addr(s)
257 .map(Self::from)
258 .or_else(|_| <Ipv6 as Afi>::Primitive::parse_addr(s).map(Self::from))
259 }
260}
261
262impl fmt::Display for Address {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 match self {
265 Self::Ipv4(addr) => addr.fmt(f),
266 Self::Ipv6(addr) => addr.fmt(f),
267 }
268 }
269}
270
271impl fmt::Debug for Address {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 write!(f, "Address<Any>::")?;
274 match self {
275 Self::Ipv4(addr) => write!(f, "Ipv4({addr})"),
276 Self::Ipv6(addr) => write!(f, "Ipv6({addr})"),
277 }
278 }
279}
280
281#[cfg(any(test, feature = "arbitrary"))]
282use proptest::{
283 arbitrary::{any, Arbitrary},
284 prop_oneof,
285 strategy::{BoxedStrategy, Strategy},
286};
287
288#[cfg(any(test, feature = "arbitrary"))]
289impl Arbitrary for Address {
290 type Parameters = ();
291 type Strategy = BoxedStrategy<Self>;
292
293 fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
294 prop_oneof![
295 any::<concrete::Address<Ipv4>>().prop_map(Self::Ipv4),
296 any::<concrete::Address<Ipv6>>().prop_map(Self::Ipv6),
297 ]
298 .boxed()
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use proptest::proptest;
305
306 use super::*;
307 use crate::traits::Address as _;
308
309 #[cfg(feature = "std")]
310 proptest! {
311 #[test]
312 fn parse_any_display(addr in any::<Address>()) {
313 use std::string::ToString as _;
314 let parsed = addr.to_string().parse::<Address>().unwrap();
315 assert_eq!(addr, parsed);
316 }
317 }
318
319 proptest! {
320 #[test]
321 fn symmetric_eq((a, b) in any::<(Address, Address)>()) {
322 assert_eq!(a.eq(&b), b.eq(&a));
323 }
324
325 #[test]
326 fn symmetric_eq_ipv4(a in any::<Address>(), b in any::<concrete::Address<Ipv4>>()) {
327 assert_eq!(a.eq(&b), b.eq(&a));
328 }
329
330 #[test]
331 fn symmetric_eq_ipv6(a in any::<Address>(), b in any::<concrete::Address<Ipv6>>()) {
332 assert_eq!(a.eq(&b), b.eq(&a));
333 }
334
335 #[test]
336 fn transitive_eq((a, b, c) in any::<(Address, Address, Address)>()) {
337 assert_eq!(a == b && b == c, a == c);
338 }
339
340 #[test]
341 fn transitive_eq_ipv4(
342 (a, c) in any::<(Address, Address)>(),
343 b in any::<concrete::Address<Ipv4>>(),
344 ) {
345 assert_eq!(a == b && b == c, a == c);
346 }
347
348 #[test]
349 fn transitive_eq_ipv6(
350 (a, c) in any::<(Address, Address)>(),
351 b in any::<concrete::Address<Ipv6>>(),
352 ) {
353 assert_eq!(a == b && b == c, a == c);
354 }
355 }
356
357 proptest! {
358 #[test]
359 fn dual_cmp((a, b) in any::<(Address, Address)>()) {
360 assert_eq!(a.partial_cmp(&b), b.partial_cmp(&a).map(Ordering::reverse));
361 }
362
363 #[test]
364 fn dual_cmp_ipv4(a in any::<Address>(), b in any::<concrete::Address<Ipv4>>()) {
365 assert_eq!(a.partial_cmp(&b), b.partial_cmp(&a).map(Ordering::reverse));
366 }
367
368 #[test]
369 fn dual_cmp_ipv6(a in any::<Address>(), b in any::<concrete::Address<Ipv6>>()) {
370 assert_eq!(a.partial_cmp(&b), b.partial_cmp(&a).map(Ordering::reverse));
371 }
372
373 #[test]
374 fn transitive_le((a, b, c) in any::<(Address, Address, Address)>()) {
375 if a < b && b < c {
376 assert!(a < c);
377 }
378 }
379
380 #[test]
381 fn transitive_le_ipv4(
382 (a, c) in any::<(Address, Address)>(),
383 b in any::<concrete::Address<Ipv4>>(),
384 ) {
385 if a < b && b < c {
386 assert!(a < c);
387 }
388 }
389
390 #[test]
391 fn transitive_le_ipv6(
392 (a, c) in any::<(Address, Address)>(),
393 b in any::<concrete::Address<Ipv6>>(),
394 ) {
395 if a < b && b < c {
396 assert!(a < c);
397 }
398 }
399
400 #[test]
401 fn transitive_ge((a, b, c) in any::<(Address, Address, Address)>()) {
402 if a > b && b > c {
403 assert!(a > c);
404 }
405 }
406
407 #[test]
408 fn transitive_ge_ipv4(
409 (a, c) in any::<(Address, Address)>(),
410 b in any::<concrete::Address<Ipv4>>(),
411 ) {
412 if a > b && b > c {
413 assert!(a > c);
414 }
415 }
416
417 #[test]
418 fn transitive_ge_ipv6(
419 (a, c) in any::<(Address, Address)>(),
420 b in any::<concrete::Address<Ipv6>>(),
421 ) {
422 if a > b && b > c {
423 assert!(a > c);
424 }
425 }
426 }
427
428 #[test]
429 fn ipv4_broadcast_is_broadcast() {
430 assert!("255.255.255.255".parse::<Address>().unwrap().is_broadcast());
431 }
432
433 #[test]
434 fn ipv4_unicast_is_not_broadcast() {
435 assert!(!"203.0.113.1".parse::<Address>().unwrap().is_broadcast());
436 }
437
438 #[test]
439 fn ipv6_all_ones_is_not_broadcast() {
440 assert!(!"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
441 .parse::<Address>()
442 .unwrap()
443 .is_broadcast());
444 }
445
446 #[test]
447 fn ipv4_link_local_is_link_local() {
448 assert!("169.254.254.1".parse::<Address>().unwrap().is_link_local());
449 }
450
451 #[test]
452 fn ipv6_link_local_is_link_local() {
453 assert!("fe80::1".parse::<Address>().unwrap().is_link_local());
454 }
455
456 #[test]
457 fn ipv4_unicast_is_not_link_local() {
458 assert!(!"203.0.113.1".parse::<Address>().unwrap().is_link_local());
459 }
460
461 #[test]
462 fn ipv6_localhost_is_not_link_local() {
463 assert!(!"::1".parse::<Address>().unwrap().is_link_local());
464 }
465
466 #[test]
467 fn ipv4_private_is_private() {
468 assert!("172.18.0.1".parse::<Address>().unwrap().is_private());
469 }
470
471 #[test]
472 fn ipv4_unicast_is_not_private() {
473 assert!(!"203.0.113.1".parse::<Address>().unwrap().is_private());
474 }
475
476 #[test]
477 fn ipv6_ula_is_not_private() {
478 assert!(!"fc01::1".parse::<Address>().unwrap().is_private());
479 }
480
481 #[test]
482 fn ipv4_reserved_is_reserved() {
483 assert!("240.0.0.1".parse::<Address>().unwrap().is_reserved());
484 }
485
486 #[test]
487 fn ipv4_broadcast_is_not_reserved() {
488 assert!(!"255.255.255.255".parse::<Address>().unwrap().is_reserved());
489 }
490
491 #[test]
492 fn ipv6_unassigned_is_not_reserved() {
493 assert!(!"4::1".parse::<Address>().unwrap().is_reserved());
494 }
495
496 #[test]
497 fn ipv4_shared_is_shared() {
498 assert!("100.72.1.1".parse::<Address>().unwrap().is_shared());
499 }
500
501 #[test]
502 fn ipv4_unicast_is_not_shared() {
503 assert!(!"192.0.2.1".parse::<Address>().unwrap().is_shared());
504 }
505
506 #[test]
507 fn ipv6_ula_is_not_shared() {
508 assert!(!"fc00::1".parse::<Address>().unwrap().is_shared());
509 }
510
511 #[test]
512 fn ipv4_thisnet_is_thisnet() {
513 assert!("0.255.255.255".parse::<Address>().unwrap().is_thisnet());
514 }
515
516 #[test]
517 fn ipv6_unspecified_is_not_thisnet() {
518 assert!(!"::".parse::<Address>().unwrap().is_thisnet());
519 }
520
521 #[test]
522 fn ipv4_benchmarking_is_benmarking() {
523 assert!("198.19.0.1".parse::<Address>().unwrap().is_benchmarking());
524 }
525
526 #[test]
527 fn ipv6_benchmarking_is_benmarking() {
528 assert!("2001:2::1".parse::<Address>().unwrap().is_benchmarking());
529 }
530
531 #[test]
532 fn ipv4_test_net_2_is_documentation() {
533 assert!("198.51.100.1"
534 .parse::<Address>()
535 .unwrap()
536 .is_documentation());
537 }
538
539 #[test]
540 fn ipv6_documentation_is_documentation() {
541 assert!("2001:db8::1".parse::<Address>().unwrap().is_documentation());
542 }
543
544 #[test]
545 fn ipv4_private_1_is_not_global() {
546 assert!(!"10.254.0.0".parse::<Address>().unwrap().is_global());
547 }
548
549 #[test]
550 fn ipv4_private_2_is_not_global() {
551 assert!(!"192.168.10.65".parse::<Address>().unwrap().is_global());
552 }
553
554 #[test]
555 fn ipv4_private_3_is_not_global() {
556 assert!(!"172.16.10.65".parse::<Address>().unwrap().is_global());
557 }
558
559 #[test]
560 fn ipv6_ula_is_not_global() {
561 assert!(!"fc00::1".parse::<Address>().unwrap().is_global());
562 }
563
564 #[test]
565 fn ipv4_thisnet_is_not_global() {
566 assert!(!"0.1.2.3".parse::<Address>().unwrap().is_global());
567 }
568
569 #[test]
570 fn ipv4_unspecified_is_not_global() {
571 assert!(!"0.0.0.0".parse::<Address>().unwrap().is_global());
572 }
573
574 #[test]
575 fn ipv6_unspecified_is_not_global() {
576 assert!(!"::".parse::<Address>().unwrap().is_global());
577 }
578
579 #[test]
580 fn ipv4_localhost_is_not_global() {
581 assert!(!"127.0.0.1".parse::<Address>().unwrap().is_global());
582 }
583
584 #[test]
585 fn ipv6_localhost_is_not_global() {
586 assert!(!"::1".parse::<Address>().unwrap().is_global());
587 }
588
589 #[test]
590 fn ipv4_link_local_is_not_global() {
591 assert!(!"169.254.45.1".parse::<Address>().unwrap().is_global());
592 }
593
594 #[test]
595 fn ipv6_link_local_is_not_global() {
596 assert!(!"fe80::1".parse::<Address>().unwrap().is_global());
597 }
598
599 #[test]
600 fn ipv4_broadcast_is_not_global() {
601 assert!(!"255.255.255.255".parse::<Address>().unwrap().is_global());
602 }
603
604 #[test]
605 fn ipv4_doc_1_is_not_global() {
606 assert!(!"192.0.2.255".parse::<Address>().unwrap().is_global());
607 }
608
609 #[test]
610 fn ipv4_doc_2_is_not_global() {
611 assert!(!"198.51.100.65".parse::<Address>().unwrap().is_global());
612 }
613
614 #[test]
615 fn ipv4_doc_3_is_not_global() {
616 assert!(!"203.0.113.6".parse::<Address>().unwrap().is_global());
617 }
618
619 #[test]
620 fn ipv6_doc_is_not_global() {
621 assert!(!"2001:db8::1".parse::<Address>().unwrap().is_global());
622 }
623
624 #[test]
625 fn ipv4_shared_is_not_global() {
626 assert!(!"100.100.0.0".parse::<Address>().unwrap().is_global());
627 }
628
629 #[test]
630 fn ipv4_proto_specific_1_is_not_global() {
631 assert!(!"192.0.0.0".parse::<Address>().unwrap().is_global());
632 }
633
634 #[test]
635 fn ipv4_proto_specific_2_is_not_global() {
636 assert!(!"192.0.0.255".parse::<Address>().unwrap().is_global());
637 }
638
639 #[test]
640 fn ipv6_proto_specific_is_not_global() {
641 assert!(!"2001:100::1".parse::<Address>().unwrap().is_global());
642 }
643
644 #[test]
645 fn ipv4_reserved_is_not_global() {
646 assert!(!"250.10.20.30".parse::<Address>().unwrap().is_global());
647 }
648
649 #[test]
650 fn ipv4_benchmarking_is_not_global() {
651 assert!(!"198.18.0.0".parse::<Address>().unwrap().is_global());
652 }
653
654 #[test]
655 fn ipv6_benchmarking_is_not_global() {
656 assert!(!"2001:2::1".parse::<Address>().unwrap().is_global());
657 }
658
659 #[test]
660 fn ipv4_local_multicast_is_not_global() {
661 assert!(!"224.0.0.1".parse::<Address>().unwrap().is_global());
662 }
663
664 #[test]
665 fn ipv4_domain_multicast_is_not_global() {
666 assert!(!"239.0.0.1".parse::<Address>().unwrap().is_global());
667 }
668
669 #[test]
670 fn ipv6_non_global_multicast_is_not_global() {
671 assert!(!"ff08::1".parse::<Address>().unwrap().is_global());
672 }
673
674 #[test]
675 fn ipv4_pcp_anycast_is_global() {
676 assert!("192.0.0.9".parse::<Address>().unwrap().is_global());
677 }
678
679 #[test]
680 fn ipv6_orchidv2_is_global() {
681 assert!("2001:20::1".parse::<Address>().unwrap().is_global());
682 }
683
684 #[test]
685 fn ipv4_global_multicast_is_global() {
686 assert!("224.0.1.1".parse::<Address>().unwrap().is_global());
687 }
688
689 #[test]
690 fn ipv6_global_multicast_is_global() {
691 assert!("ff0e::1".parse::<Address>().unwrap().is_global());
692 }
693
694 #[test]
695 fn ipv4_global_unicast_is_global() {
696 assert!("1.1.1.1".parse::<Address>().unwrap().is_global());
697 }
698
699 #[test]
700 fn ipv6_global_unicast_is_global() {
701 assert!("2606:4700:4700::1111"
702 .parse::<Address>()
703 .unwrap()
704 .is_global());
705 }
706
707 #[test]
708 fn ipv4_loopback_is_loopback() {
709 assert!("127.0.0.53".parse::<Address>().unwrap().is_loopback());
710 }
711
712 #[test]
713 fn ipv6_loopback_is_loopback() {
714 assert!("::1".parse::<Address>().unwrap().is_loopback());
715 }
716
717 #[test]
718 fn ipv4_multicast_is_multicast() {
719 assert!("224.254.0.0".parse::<Address>().unwrap().is_multicast());
720 }
721
722 #[test]
723 fn ipv4_unicast_is_not_multicast() {
724 assert!(!"172.16.10.65".parse::<Address>().unwrap().is_multicast());
725 }
726
727 #[test]
728 fn ipv6_multicast_is_multicast() {
729 assert!("ff01::1".parse::<Address>().unwrap().is_multicast());
730 }
731
732 #[test]
733 fn ipv4_unspecified_is_unspecified() {
734 assert!("0.0.0.0".parse::<Address>().unwrap().is_unspecified());
735 }
736
737 #[test]
738 fn ipv6_unspecified_is_unspecified() {
739 assert!("::".parse::<Address>().unwrap().is_unspecified());
740 }
741
742 #[test]
743 fn ipv6_ula_is_unique_local() {
744 assert!("fc01::1".parse::<Address>().unwrap().is_unique_local());
745 }
746
747 #[test]
748 fn ipv6_doc_is_not_unique_local() {
749 assert!(!"2001:db8::1".parse::<Address>().unwrap().is_unique_local());
750 }
751
752 #[test]
753 fn ipv4_private_is_not_unique_local() {
754 assert!(!"192.168.1.1".parse::<Address>().unwrap().is_unique_local());
755 }
756
757 #[test]
758 fn ipv6_unicast_is_unicast() {
759 assert!("2001:db8::1".parse::<Address>().unwrap().is_unicast());
760 }
761 #[test]
762 fn ipv4_unicast_is_unicast() {
763 assert!("192.168.1.1".parse::<Address>().unwrap().is_unicast());
764 }
765 #[test]
766 fn ipv6_multicast_is_not_unicast() {
767 assert!(!"ffaa::1".parse::<Address>().unwrap().is_unicast());
768 }
769 #[test]
770 fn ipv4_multicast_is_not_unicast() {
771 assert!(!"239.0.0.1".parse::<Address>().unwrap().is_unicast());
772 }
773 #[test]
774 fn ipv4_broadcast_is_not_unicast() {
775 assert!(!"255.255.255.255".parse::<Address>().unwrap().is_unicast());
776 }
777
778 #[test]
779 fn ipv4_unicast_global_is_unicast_global() {
780 assert!("1.1.1.1".parse::<Address>().unwrap().is_unicast_global());
781 }
782 #[test]
783 fn ipv6_unicast_global_is_unicast_global() {
784 assert!("2606:4700:4700::1111"
785 .parse::<Address>()
786 .unwrap()
787 .is_unicast_global());
788 }
789 #[test]
790 fn ipv4_unicast_private_is_not_unicast_global() {
791 assert!(!"192.168.1.1"
792 .parse::<Address>()
793 .unwrap()
794 .is_unicast_global());
795 }
796 #[test]
797 fn ipv4_multicast_global_is_not_unicast_global() {
798 assert!(!"225.0.0.1".parse::<Address>().unwrap().is_unicast_global());
799 }
800 #[test]
801 fn ipv6_unicast_documentation_is_not_unicast_global() {
802 assert!(!"2001:db8::1"
803 .parse::<Address>()
804 .unwrap()
805 .is_unicast_global());
806 }
807 #[test]
808 fn ipv6_multicast_global_is_not_unicast_global() {
809 assert!(!"ff0e::1".parse::<Address>().unwrap().is_unicast_global());
810 }
811}