ingot_types/
ip.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Minimal types for IPv4 and IPv6 addresses
6//!
7//! These addresses can be translated into [`core::net`] addresses at no cost,
8//! but they also implement traits from [`zerocopy`] for zero-copy parsing.
9
10use crate::zerocopy_type;
11
12zerocopy_type!(
13    /// An IPv4 address
14    pub struct Ipv4Addr {
15        inner: [u8; 4],
16    }
17);
18
19impl Ipv4Addr {
20    /// An IPv4 address representing an unspecified address: `0.0.0.0`
21    pub const UNSPECIFIED: Self = Self { inner: [0; 4] };
22
23    /// Return the bytes of the address.
24    #[inline]
25    pub const fn octets(&self) -> [u8; 4] {
26        self.inner
27    }
28
29    /// Builds a new address from bytes
30    #[inline]
31    pub const fn from_octets(bytes: [u8; 4]) -> Self {
32        Self { inner: bytes }
33    }
34
35    /// Private function to convert to a `core::net::Ipv4Addr`
36    /// in a const context as `From` implementations are not
37    /// allowed in const contexts.
38    ///
39    /// This can be simplied once [`from_octets` and `from_segements`] is
40    /// stabilized.
41    ///
42    /// [`from_octets` and `from_segements`]: https://github.com/rust-lang/rust/issues/131360
43    #[inline]
44    const fn into_core(self) -> core::net::Ipv4Addr {
45        core::net::Ipv4Addr::new(
46            self.inner[0],
47            self.inner[1],
48            self.inner[2],
49            self.inner[3],
50        )
51    }
52
53    /// Returns true if the address is a multicast address.
54    #[inline]
55    pub const fn is_multicast(&self) -> bool {
56        self.into_core().is_multicast()
57    }
58
59    /// Returns true if the address is a local broadcast address.
60    #[inline]
61    pub const fn is_broadcast(&self) -> bool {
62        self.into_core().is_broadcast()
63    }
64
65    /// Returns true if the address is a private address.
66    #[inline]
67    pub const fn is_private(&self) -> bool {
68        self.into_core().is_private()
69    }
70
71    /// Returns true if the address is a loopback address.
72    #[inline]
73    pub const fn is_loopback(&self) -> bool {
74        self.into_core().is_loopback()
75    }
76
77    /// Returns true if the address is a unicast address.
78    #[inline]
79    pub const fn is_unicast(&self) -> bool {
80        !self.is_multicast() && !self.is_broadcast()
81    }
82
83    /// Returns true if the address is a link-local address.
84    #[inline]
85    pub const fn is_link_local(&self) -> bool {
86        self.into_core().is_link_local()
87    }
88
89    /// Returns true if the address is a global unicast address.
90    #[inline]
91    pub const fn is_global(&self) -> bool {
92        !self.is_multicast()
93            && !self.is_private()
94            && !self.is_loopback()
95            && !self.is_link_local()
96            && !self.is_broadcast()
97    }
98
99    /// Returns true if the address is a documentation address.
100    /// There are three such unicast ranges [IETF RFC 5737]:
101    /// * 192.0.2.0/24
102    /// * 198.51.100.0/24
103    /// * 203.0.113.0/24
104    ///
105    /// And one multicast ([IETF RFC 5771] / [IETF RFC 6676]) one:
106    /// * 233.252.0.0/24
107    ///
108    /// [IETF RFC 5737]: https://tools.ietf.org/html/rfc5737
109    /// [IETF RFC 5771]: https://tools.ietf.org/html/rfc5771
110    /// [IETF RFC 6676]: https://tools.ietf.org/html/rfc6676
111    #[inline]
112    pub const fn is_documentation(&self) -> bool {
113        matches!(
114            self.octets(),
115            [192, 0, 2, _]
116                | [198, 51, 100, _]
117                | [203, 0, 113, _]
118                | [233, 252, 0, _]
119        )
120    }
121
122    /// Returns true if the address is a reserved address.
123    ///
124    /// Note: The underlying `core::net` version is not yet stable as
125    /// of Rust 1.84.1.
126    #[inline]
127    pub const fn is_reserved(&self) -> bool {
128        self.octets()[0] & 240 == 240 && !self.is_broadcast()
129    }
130}
131
132impl From<core::net::Ipv4Addr> for Ipv4Addr {
133    #[inline]
134    fn from(ip4: core::net::Ipv4Addr) -> Self {
135        Self { inner: ip4.octets() }
136    }
137}
138
139impl From<Ipv4Addr> for core::net::Ipv4Addr {
140    #[inline]
141    fn from(ip4: Ipv4Addr) -> Self {
142        Self::from(ip4.inner)
143    }
144}
145
146zerocopy_type!(
147    /// An IPv6 address.
148    pub struct Ipv6Addr {
149        inner: [u8; 16],
150    }
151);
152
153impl Ipv6Addr {
154    /// The unspecified IPv6 address, i.e., `::` or all zeros.
155    pub const UNSPECIFIED: Self = Self { inner: [0; 16] };
156
157    /// An IPv6 address representing localhost `::1`
158    pub const LOCALHOST: Self =
159        Self { inner: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] };
160
161    /// Return the bytes of the address.
162    #[inline]
163    pub const fn octets(&self) -> [u8; 16] {
164        self.inner
165    }
166
167    /// Creates an `Ipv6Addr` from a sixteen element byte array.
168    #[inline]
169    pub const fn from_octets(bytes: [u8; 16]) -> Self {
170        Self { inner: bytes }
171    }
172
173    /// Creates an `Ipv6Addr` from an eight element 16-bit array.
174    #[inline]
175    pub const fn from_segments(words: [u16; 8]) -> Self {
176        let w0 = words[0].to_be_bytes();
177        let w1 = words[1].to_be_bytes();
178        let w2 = words[2].to_be_bytes();
179        let w3 = words[3].to_be_bytes();
180        let w4 = words[4].to_be_bytes();
181        let w5 = words[5].to_be_bytes();
182        let w6 = words[6].to_be_bytes();
183        let w7 = words[7].to_be_bytes();
184        Self {
185            inner: [
186                w0[0], w0[1], w1[0], w1[1], w2[0], w2[1], w3[0], w3[1], w4[0],
187                w4[1], w5[0], w5[1], w6[0], w6[1], w7[0], w7[1],
188            ],
189        }
190    }
191
192    /// Returns an eight element 16-bit array representation of the address.
193    ///
194    /// This is taken from the core `Ipv6Addr` implementation.
195    #[inline]
196    pub const fn segments(&self) -> [u16; 8] {
197        // All elements in `self.octets` must be big endian.
198        // SAFETY: `[u8; 16]` is always safe to transmute to `[u16; 8]`.
199        let [a, b, c, d, e, f, g, h] = unsafe {
200            core::mem::transmute::<[u8; 16], [u16; 8]>(self.octets())
201        };
202        // We want native endian u16
203        [
204            u16::from_be(a),
205            u16::from_be(b),
206            u16::from_be(c),
207            u16::from_be(d),
208            u16::from_be(e),
209            u16::from_be(f),
210            u16::from_be(g),
211            u16::from_be(h),
212        ]
213    }
214
215    /// Private function to convert to a `core::net::Ipv6Addr`
216    /// in a const context as `From` implementations are not
217    /// yet allowed in const contexts.
218    /// This can be simplied once [`from_octets` and `from_segements`] is
219    /// stabilized.
220    ///
221    /// [`from_octets` and `from_segements`]: https://github.com/rust-lang/rust/issues/131360
222    #[inline]
223    const fn into_core(self) -> core::net::Ipv6Addr {
224        let segments = self.segments();
225        core::net::Ipv6Addr::new(
226            segments[0],
227            segments[1],
228            segments[2],
229            segments[3],
230            segments[4],
231            segments[5],
232            segments[6],
233            segments[7],
234        )
235    }
236
237    /// Returns true if the address is a multicast address.
238    #[inline]
239    pub const fn is_multicast(&self) -> bool {
240        self.into_core().is_multicast()
241    }
242
243    /// Returns true if the address is a loopback address.
244    #[inline]
245    pub const fn is_loopback(&self) -> bool {
246        self.into_core().is_loopback()
247    }
248
249    /// Returns true if the address is a unicast address.
250    #[inline]
251    pub const fn is_unicast(&self) -> bool {
252        !self.is_multicast()
253    }
254
255    /// Returns true if the address is a unicast link-local address.
256    ///
257    /// Note: The underlying `core::net` version is not yet stable as
258    /// of Rust 1.84.1.
259    #[inline]
260    pub const fn is_unicast_link_local(&self) -> bool {
261        (self.segments()[0] & 0xffc0) == 0xfe80
262    }
263
264    /// Returns true if the address is a unique local address.
265    ///
266    /// Note: The underlying `core::net` version is not yet stable as
267    /// of Rust 1.84.1.
268    #[inline]
269    pub const fn is_unique_local(&self) -> bool {
270        (self.segments()[0] & 0xfe00) == 0xfc00
271    }
272
273    /// Returns true if the address is a global unicast address.
274    #[inline]
275    pub const fn is_unicast_global(&self) -> bool {
276        !self.is_multicast()
277            && !self.is_unicast_link_local()
278            && !self.is_unique_local()
279    }
280
281    /// Returns true if the address is a documentation address.
282    ///
283    /// Defined in [IETF RFC 3849].
284    ///
285    /// Note: The underlying `core::net` version is not yet stable as
286    /// of Rust 1.84.1.
287    ///
288    /// [IETF RFC 3849]: https://tools.ietf.org/html/rfc3849
289    #[inline]
290    pub const fn is_documentation(&self) -> bool {
291        let segments = self.segments();
292        (segments[0] == 0x2001) && (segments[1] == 0xdb8)
293    }
294}
295
296impl From<core::net::Ipv6Addr> for Ipv6Addr {
297    #[inline]
298    fn from(ip6: core::net::Ipv6Addr) -> Self {
299        Self { inner: ip6.octets() }
300    }
301}
302
303impl From<Ipv6Addr> for core::net::Ipv6Addr {
304    #[inline]
305    fn from(ip6: Ipv6Addr) -> Self {
306        Self::from(ip6.inner)
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use super::*;
313
314    #[test]
315    fn ipv4() {
316        let addr = Ipv4Addr::from_octets([192, 168, 1, 1]);
317        assert!(addr.is_private());
318        assert!(!addr.is_global());
319        assert!(!addr.is_multicast());
320        assert!(!addr.is_broadcast());
321        assert!(!addr.is_loopback());
322        assert!(addr.is_unicast());
323        assert!(!addr.is_link_local());
324        assert!(!addr.is_documentation());
325        assert!(!addr.is_reserved());
326    }
327
328    #[test]
329    fn ipv4_broadcast() {
330        let addr = Ipv4Addr::from_octets([255, 255, 255, 255]);
331        assert!(!addr.is_private());
332        assert!(!addr.is_global());
333        assert!(!addr.is_multicast());
334        assert!(addr.is_broadcast());
335        assert!(!addr.is_unicast());
336        assert!(!addr.is_loopback());
337        assert!(!addr.is_link_local());
338        assert!(!addr.is_documentation());
339        assert!(!addr.is_reserved());
340    }
341
342    #[test]
343    fn ipv4_loopback() {
344        let addr = Ipv4Addr::from_octets([127, 0, 0, 1]);
345        assert!(!addr.is_private());
346        assert!(!addr.is_global());
347        assert!(!addr.is_multicast());
348        assert!(!addr.is_broadcast());
349        assert!(addr.is_loopback());
350        assert!(addr.is_unicast());
351        assert!(!addr.is_link_local());
352        assert!(!addr.is_documentation());
353        assert!(!addr.is_reserved());
354    }
355
356    #[test]
357    fn ipv6() {
358        let addr = Ipv6Addr::from_octets([
359            0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
360        ]);
361        assert!(!addr.is_multicast());
362        assert!(addr.is_unicast());
363        assert!(!addr.is_unicast_link_local());
364        assert!(!addr.is_unique_local());
365        assert!(addr.is_documentation());
366        assert!(addr.is_unicast_global());
367    }
368
369    #[test]
370    fn ipv6_link_local() {
371        let addr = Ipv6Addr::from_octets([
372            0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef,
373        ]);
374        assert!(!addr.is_multicast());
375        assert!(addr.is_unicast());
376        assert!(addr.is_unicast_link_local());
377        assert!(!addr.is_unique_local());
378        assert!(!addr.is_documentation());
379        assert!(!addr.is_unicast_global());
380    }
381}