ip/concrete/addr/
ipv6.rs

1use super::Address;
2use crate::{
3    any,
4    concrete::{Ipv4, Ipv6},
5    traits::{primitive::IntoIpv6Segments as _, Address as _, Afi},
6};
7
8// TODO: make methods `const fn`
9impl Address<Ipv6> {
10    /// Returns [`true`] if the address is unicast link local.
11    ///
12    /// This method is provided for compatibility with [`std::net::Ipv6Addr`],
13    /// and is just a wrapper around
14    /// [`Address::is_link_local()`][crate::traits::Address::is_link_local()].
15    #[must_use]
16    pub fn is_unicast_link_local(&self) -> bool {
17        self.is_link_local()
18    }
19
20    /// Returns the [`Ipv6MulticastScope`][MulticastScope] variant of the
21    /// address if the address is a multicast address, or [`None`]
22    /// otherwise.
23    ///
24    /// # Examples
25    ///
26    /// ``` rust
27    /// use ip::{concrete::Ipv6MulticastScope, Address, Ipv6};
28    ///
29    /// let ipv6_site_local_multicast = "ff05::1".parse::<Address<Ipv6>>()?;
30    /// assert_eq!(
31    ///     ipv6_site_local_multicast.multicast_scope(),
32    ///     Some(Ipv6MulticastScope::SiteLocal),
33    /// );
34    ///
35    /// let ipv6_global_multicast = "ff0e::1".parse::<Address<Ipv6>>()?;
36    /// assert_eq!(
37    ///     ipv6_global_multicast.multicast_scope(),
38    ///     Some(Ipv6MulticastScope::Global),
39    /// );
40    ///
41    /// let ipv6_unicast = "2001:db8::1".parse::<Address<Ipv6>>()?;
42    /// assert_eq!(ipv6_unicast.multicast_scope(), None,);
43    /// # Ok::<(), ip::Error>(())
44    /// ```
45    #[allow(clippy::match_same_arms)]
46    #[must_use]
47    pub fn multicast_scope(&self) -> Option<MulticastScope> {
48        if self.is_multicast() {
49            match self.octets()[1] & 0x0f {
50                0x0 => Some(MulticastScope::Reserved),
51                0x1 => Some(MulticastScope::InterfaceLocal),
52                0x2 => Some(MulticastScope::LinkLocal),
53                0x3 => Some(MulticastScope::RealmLocal),
54                0x4 => Some(MulticastScope::AdminLocal),
55                0x5 => Some(MulticastScope::SiteLocal),
56                0x6..=0x7 => Some(MulticastScope::Unassigned),
57                0x8 => Some(MulticastScope::OrganizationLocal),
58                0x9..=0xd => Some(MulticastScope::Unassigned),
59                0xe => Some(MulticastScope::Global),
60                0xf => Some(MulticastScope::Reserved),
61                _ => unreachable!(),
62            }
63        } else {
64            None
65        }
66    }
67
68    /// Returns a big-endian [`[u16; 8]`] representing the segments of the
69    /// address.
70    ///
71    /// # Examples
72    ///
73    /// ``` rust
74    /// use ip::{Address, Ipv6};
75    ///
76    /// assert_eq!(
77    ///     "2001:db8:f00::1".parse::<Address<Ipv6>>()?.segments(),
78    ///     [0x2001, 0xdb8, 0xf00, 0x0, 0x0, 0x0, 0x0, 0x1],
79    /// );
80    /// # Ok::<(), ip::Error>(())
81    /// ```
82    #[must_use]
83    pub fn segments(&self) -> [u16; 8] {
84        self.into_primitive().into_segments()
85    }
86
87    // TODO: move to `traits::Address`
88    /// Convert the address to its canonical representation as an
89    /// [`any::Address`], by converting an IPv4-mapped address to a
90    /// [`any::Address::Ipv4`], and returning an [`any::Address::Ipv6`]
91    /// otherwise.
92    ///
93    /// # Examples
94    ///
95    /// ``` rust
96    /// use ip::{Address, Any, Ipv6};
97    ///
98    /// let ipv4_mapped = "::ffff:192.168.1.1".parse::<Address<Ipv6>>()?;
99    /// assert_eq!(
100    ///     ipv4_mapped.to_canonical(),
101    ///     "192.168.1.1".parse::<Address<Any>>()?,
102    /// );
103    ///
104    /// let ipv6_unicast = "2001:db8::1".parse::<Address<Ipv6>>()?;
105    /// assert_eq!(
106    ///     ipv6_unicast.to_canonical(),
107    ///     Address::<Any>::Ipv6(ipv6_unicast),
108    /// );
109    /// # Ok::<(), ip::Error>(())
110    /// ```
111    #[allow(clippy::wrong_self_convention)]
112    #[allow(clippy::option_if_let_else)]
113    #[must_use]
114    pub fn to_canonical(&self) -> any::Address {
115        if let Some(ipv4_addr) = self.to_ipv4_mapped() {
116            any::Address::Ipv4(ipv4_addr)
117        } else {
118            any::Address::Ipv6(*self)
119        }
120    }
121
122    /// Returns the embedded [`Address<Ipv4>`] in an IPv4-compatible or
123    /// IPv4-mapped [`Address<Ipv6>`], or [`None`] otherwise.
124    ///
125    /// # Examples
126    ///
127    /// ``` rust
128    /// use ip::{Address, Ipv6};
129    ///
130    /// assert_eq!(
131    ///     "::192.168.1.1"
132    ///         .parse::<Address<Ipv6>>()?
133    ///         .to_ipv4()
134    ///         .map(|ipv4| ipv4.octets()),
135    ///     Some([192, 168, 1, 1]),
136    /// );
137    ///
138    /// assert_eq!("2001:db8::1".parse::<Address<Ipv6>>()?.to_ipv4(), None,);
139    /// # Ok::<(), ip::Error>(())
140    /// ```
141    #[allow(clippy::wrong_self_convention)]
142    #[must_use]
143    pub fn to_ipv4(&self) -> Option<Address<Ipv4>> {
144        self.to_ipv4_mapped().or_else(|| match self.octets() {
145            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, octets @ ..] => Some(Address::new(
146                <Ipv4 as Afi>::Primitive::from_be_bytes(octets),
147            )),
148            _ => None,
149        })
150    }
151
152    /// Returns the embedded [`Address<Ipv4>`] in an IPv4-mapped
153    /// [`Address<Ipv6>`], or [`None`] otherwise.
154    ///
155    /// # Examples
156    ///
157    /// ``` rust
158    /// use ip::{Address, Ipv6};
159    ///
160    /// assert_eq!(
161    ///     "::ffff:172.16.1.1"
162    ///         .parse::<Address<Ipv6>>()?
163    ///         .to_ipv4_mapped()
164    ///         .map(|ipv4| ipv4.octets()),
165    ///     Some([172, 16, 1, 1]),
166    /// );
167    ///
168    /// assert_eq!(
169    ///     "::192.168.1.1".parse::<Address<Ipv6>>()?.to_ipv4_mapped(),
170    ///     None,
171    /// );
172    ///
173    /// assert_eq!(
174    ///     "2001:db8::1".parse::<Address<Ipv6>>()?.to_ipv4_mapped(),
175    ///     None,
176    /// );
177    /// # Ok::<(), ip::Error>(())
178    /// ```
179    #[allow(clippy::wrong_self_convention)]
180    #[must_use]
181    pub fn to_ipv4_mapped(&self) -> Option<Address<Ipv4>> {
182        match self.octets() {
183            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, octets @ ..] => Some(Address::new(
184                <Ipv4 as Afi>::Primitive::from_be_bytes(octets),
185            )),
186            _ => None,
187        }
188    }
189}
190
191// TODO: document omission of `non_exhaustive`
192/// IPv6 multicast address scopes, as defined in [RFC 4291].
193///
194/// See also [`Address::multicast_scope()`].
195///
196/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
197#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
198pub enum MulticastScope {
199    /// Reserved.
200    Reserved,
201    /// Scope values not yet assigned to a multicast scope.
202    Unassigned,
203    /// Interface local scope.
204    InterfaceLocal,
205    /// Link local scope.
206    LinkLocal,
207    /// Realm local scope.
208    RealmLocal,
209    /// Locally administered scope.
210    AdminLocal,
211    /// Site local scope.
212    SiteLocal,
213    /// Organization local scope.
214    OrganizationLocal,
215    /// Global scope.
216    Global,
217}