Skip to main content

oxnet/
ipnet.rs

1// Copyright 2025 Oxide Computer Company
2
3use std::{
4    net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
5    num::ParseIntError,
6};
7
8#[cfg(feature = "ula")]
9use {
10    rand::Rng,
11    std::time::{SystemTime, SystemTimeError},
12};
13
14/// A prefix error during the creation of an [IpNet], [Ipv4Net], or [Ipv6Net]
15#[derive(Debug, Clone, PartialEq)]
16pub struct IpNetPrefixError(u8);
17
18impl std::fmt::Display for IpNetPrefixError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "invalid network prefix {}", self.0)
21    }
22}
23impl std::error::Error for IpNetPrefixError {}
24
25/// An error during the parsing of an [IpNet], [Ipv4Net], or [Ipv6Net]
26#[derive(Debug, Clone)]
27pub enum IpNetParseError {
28    /// Failure to parse the address
29    InvalidAddr(AddrParseError),
30    /// Bad prefix value
31    PrefixValue(IpNetPrefixError),
32    /// No slash to indicate the prefix
33    NoPrefix,
34    /// Prefix parse error
35    InvalidPrefix(ParseIntError),
36}
37
38impl std::fmt::Display for IpNetParseError {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            IpNetParseError::InvalidAddr(e) => e.fmt(f),
42            IpNetParseError::PrefixValue(e) => {
43                write!(f, "invalid prefix value: {e}")
44            }
45            IpNetParseError::NoPrefix => write!(f, "missing '/' character"),
46            IpNetParseError::InvalidPrefix(e) => e.fmt(f),
47        }
48    }
49}
50impl std::error::Error for IpNetParseError {}
51
52/// A subnet, either IPv4 or IPv6
53#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
54#[cfg_attr(feature = "serde", serde(untagged))]
55#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
56pub enum IpNet {
57    /// An IPv4 subnet
58    V4(Ipv4Net),
59    /// An IPv6 subnet
60    V6(Ipv6Net),
61}
62
63impl IpNet {
64    /// Create an IpNet with the given address and prefix width.
65    pub fn new(addr: IpAddr, prefix: u8) -> Result<Self, IpNetPrefixError> {
66        match addr {
67            IpAddr::V4(addr) => Ok(Self::V4(Ipv4Net::new(addr, prefix)?)),
68            IpAddr::V6(addr) => Ok(Self::V6(Ipv6Net::new(addr, prefix)?)),
69        }
70    }
71
72    /// Create an IpNet with the given address and prefix width with no checks
73    /// for the validity of the prefix length.
74    pub const fn new_unchecked(addr: IpAddr, prefix: u8) -> Self {
75        match addr {
76            IpAddr::V4(addr) => Self::V4(Ipv4Net::new_unchecked(addr, prefix)),
77            IpAddr::V6(addr) => Self::V6(Ipv6Net::new_unchecked(addr, prefix)),
78        }
79    }
80
81    /// Create an IpNet that contains *exclusively* the given address.
82    pub fn host_net(addr: IpAddr) -> Self {
83        match addr {
84            IpAddr::V4(addr) => Self::V4(Ipv4Net::host_net(addr)),
85            IpAddr::V6(addr) => Self::V6(Ipv6Net::host_net(addr)),
86        }
87    }
88
89    /// Return the base address.
90    pub const fn addr(&self) -> IpAddr {
91        match self {
92            IpNet::V4(inner) => IpAddr::V4(inner.addr()),
93            IpNet::V6(inner) => IpAddr::V6(inner.addr()),
94        }
95    }
96
97    /// Return the prefix address (the base address with the mask applied).
98    pub fn prefix(&self) -> IpAddr {
99        match self {
100            IpNet::V4(inner) => inner.prefix().into(),
101            IpNet::V6(inner) => inner.prefix().into(),
102        }
103    }
104
105    /// Return the prefix length.
106    pub const fn width(&self) -> u8 {
107        match self {
108            IpNet::V4(inner) => inner.width(),
109            IpNet::V6(inner) => inner.width(),
110        }
111    }
112
113    /// Return the netmask address derived from prefix length.
114    pub fn mask_addr(&self) -> IpAddr {
115        match self {
116            IpNet::V4(inner) => inner.mask_addr().into(),
117            IpNet::V6(inner) => inner.mask_addr().into(),
118        }
119    }
120
121    /// Return `true` iff the subnet contains only the base address i.e. the
122    /// size is exactly one address.
123    pub const fn is_host_net(&self) -> bool {
124        match self {
125            IpNet::V4(inner) => inner.is_host_net(),
126            IpNet::V6(inner) => inner.is_host_net(),
127        }
128    }
129
130    /// Return `true` iff the base address corresponds to the all-zeroes host
131    /// ID in the subnet.
132    pub fn is_network_address(&self) -> bool {
133        match self {
134            IpNet::V4(inner) => inner.is_network_address(),
135            IpNet::V6(inner) => inner.is_network_address(),
136        }
137    }
138
139    /// Return `true` iff this subnet is in a multicast address range.
140    pub const fn is_multicast(&self) -> bool {
141        match self {
142            IpNet::V4(inner) => inner.is_multicast(),
143            IpNet::V6(inner) => inner.is_multicast(),
144        }
145    }
146
147    /// Return `true` iff this subnet is in an administratively scoped
148    /// multicast address range with boundaries that are administratively
149    /// configured.
150    ///
151    /// "Admin" is short for "administratively"; these scopes have boundaries
152    /// configured by network administrators, unlike well-known scopes like
153    /// link-local or global.
154    ///
155    /// For IPv4, this is 239.0.0.0/8 as defined in [RFC 2365] and [RFC 5771].
156    /// For IPv6, this includes scopes 4, 5, and 8 (admin-local, site-local,
157    /// organization-local) as defined in [RFC 7346] and [RFC 4291].
158    ///
159    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
160    /// [RFC 5771]: https://tools.ietf.org/html/rfc5771
161    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
162    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
163    pub const fn is_admin_scoped_multicast(&self) -> bool {
164        match self {
165            IpNet::V4(inner) => inner.is_admin_scoped_multicast(),
166            IpNet::V6(inner) => inner.is_admin_scoped_multicast(),
167        }
168    }
169
170    /// Return `true` iff this subnet is in an admin-local multicast address
171    /// range (scope 4) as defined in [RFC 7346] and [RFC 4291].
172    /// This is only defined for IPv6. IPv4 does not have an equivalent
173    /// "admin-local" scope.
174    ///
175    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
176    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
177    pub const fn is_admin_local_multicast(&self) -> bool {
178        match self {
179            IpNet::V4(_inner) => false,
180            IpNet::V6(inner) => inner.is_admin_local_multicast(),
181        }
182    }
183
184    /// Return `true` iff this subnet is in a local multicast address range.
185    /// For IPv4, this is 239.255.0.0/16 (IPv4 Local Scope) as defined in
186    /// [RFC 2365]. IPv6 does not have an equivalent "local" scope.
187    ///
188    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
189    pub const fn is_local_multicast(&self) -> bool {
190        match self {
191            IpNet::V4(inner) => inner.is_local_multicast(),
192            IpNet::V6(_inner) => false,
193        }
194    }
195
196    /// Return `true` iff this subnet is in a site-local multicast address
197    /// range. This is only defined for IPv6 (scope 5) as defined in [RFC 7346]
198    /// and [RFC 4291]. IPv4 does not have a site-local multicast scope.
199    ///
200    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
201    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
202    pub const fn is_site_local_multicast(&self) -> bool {
203        match self {
204            IpNet::V4(_inner) => false,
205            IpNet::V6(inner) => inner.is_site_local_multicast(),
206        }
207    }
208
209    /// Return `true` iff this subnet is in an organization-local multicast
210    /// address range.
211    ///
212    /// For IPv4, this is 239.192.0.0/14 as defined in [RFC 2365].
213    /// For IPv6, this is scope 8 as defined in [RFC 7346] and [RFC 4291].
214    ///
215    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
216    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
217    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
218    pub const fn is_org_local_multicast(&self) -> bool {
219        match self {
220            IpNet::V4(inner) => inner.is_org_local_multicast(),
221            IpNet::V6(inner) => inner.is_org_local_multicast(),
222        }
223    }
224
225    /// Return `true` iff this subnet is in a Unique Local Address range.
226    /// This is only valid for IPv6 addresses.
227    pub const fn is_unique_local(&self) -> bool {
228        match self {
229            IpNet::V4(_inner) => false, // IPv4 does not support ULA
230            IpNet::V6(inner) => inner.is_unique_local(),
231        }
232    }
233
234    /// Return `true` iff this subnet is in a loopback address range.
235    pub const fn is_loopback(&self) -> bool {
236        match self {
237            IpNet::V4(inner) => inner.is_loopback(),
238            IpNet::V6(inner) => inner.is_loopback(),
239        }
240    }
241
242    /// Return `true` if the provided address is contained in self.
243    ///
244    /// This returns `false` if the address and the network are of different IP
245    /// families.
246    pub fn contains(&self, addr: IpAddr) -> bool {
247        match (self, addr) {
248            (IpNet::V4(net), IpAddr::V4(ip)) => net.contains(ip),
249            (IpNet::V6(net), IpAddr::V6(ip)) => net.contains(ip),
250            (_, _) => false,
251        }
252    }
253
254    /// Returns `true` iff this subnet is wholly contained within `other`.
255    ///
256    /// This returns `false` if the address and the network are of different IP
257    /// families.
258    pub fn is_subnet_of(&self, other: &Self) -> bool {
259        match (self, other) {
260            (IpNet::V4(net), IpNet::V4(other)) => net.is_subnet_of(other),
261            (IpNet::V6(net), IpNet::V6(other)) => net.is_subnet_of(other),
262            (_, _) => false,
263        }
264    }
265
266    /// Returns `true` iff `other` is wholly contained within this subnet.
267    ///
268    /// This returns `false` if the address and the network are of different IP
269    /// families.
270    pub fn is_supernet_of(&self, other: &Self) -> bool {
271        other.is_subnet_of(self)
272    }
273
274    /// Return `true` if the provided `IpNet` shares any IP addresses with
275    /// `self` (e.g., `self.is_subnet_of(other)`, or vice-versa).
276    ///
277    /// This returns `false` if the networks are of different IP families.
278    pub fn overlaps(&self, other: &Self) -> bool {
279        match (self, other) {
280            (IpNet::V4(net), IpNet::V4(other)) => net.overlaps(other),
281            (IpNet::V6(net), IpNet::V6(other)) => net.overlaps(other),
282            (_, _) => false,
283        }
284    }
285
286    /// Return `true` if this is an IPv4 network.
287    pub const fn is_ipv4(&self) -> bool {
288        matches!(self, IpNet::V4(_))
289    }
290
291    /// Return `true` if this is an IPv6 network.
292    pub const fn is_ipv6(&self) -> bool {
293        matches!(self, IpNet::V6(_))
294    }
295}
296
297impl From<Ipv4Net> for IpNet {
298    fn from(n: Ipv4Net) -> IpNet {
299        IpNet::V4(n)
300    }
301}
302
303impl From<Ipv6Net> for IpNet {
304    fn from(n: Ipv6Net) -> IpNet {
305        IpNet::V6(n)
306    }
307}
308
309impl std::fmt::Display for IpNet {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        match self {
312            IpNet::V4(inner) => write!(f, "{inner}"),
313            IpNet::V6(inner) => write!(f, "{inner}"),
314        }
315    }
316}
317
318impl std::str::FromStr for IpNet {
319    type Err = IpNetParseError;
320
321    fn from_str(s: &str) -> Result<Self, Self::Err> {
322        let Some((addr_str, prefix_str)) = s.split_once('/') else {
323            return Err(IpNetParseError::NoPrefix);
324        };
325
326        let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
327        let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
328        IpNet::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
329    }
330}
331
332#[cfg(feature = "schemars")]
333impl schemars::JsonSchema for IpNet {
334    fn schema_name() -> String {
335        "IpNet".to_string()
336    }
337
338    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
339        use crate::schema_util::label_schema;
340        schemars::schema::SchemaObject {
341            subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
342                one_of: Some(vec![
343                    label_schema("v4", gen.subschema_for::<Ipv4Net>()),
344                    label_schema("v6", gen.subschema_for::<Ipv6Net>()),
345                ]),
346                ..Default::default()
347            })),
348            extensions: crate::schema_util::extension("IpNet", "0.1.0"),
349            ..Default::default()
350        }
351        .into()
352    }
353}
354
355/// The maximum width of an IPv4 subnet
356pub const IPV4_NET_WIDTH_MAX: u8 = 32;
357
358/// An IPv4 subnet
359#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
360pub struct Ipv4Net {
361    addr: Ipv4Addr,
362    width: u8,
363}
364
365impl Ipv4Net {
366    /// Create an Ipv4Net with the given address and prefix width.
367    pub fn new(addr: Ipv4Addr, width: u8) -> Result<Self, IpNetPrefixError> {
368        if width > IPV4_NET_WIDTH_MAX {
369            Err(IpNetPrefixError(width))
370        } else {
371            Ok(Self { addr, width })
372        }
373    }
374
375    /// Create an Ipv4Net with the given address and prefix width with no
376    /// checks for the validity of the prefix length.
377    pub const fn new_unchecked(addr: Ipv4Addr, width: u8) -> Self {
378        Self { addr, width }
379    }
380
381    /// Create an Ipv4Net that contains *exclusively* the given address.
382    pub const fn host_net(addr: Ipv4Addr) -> Self {
383        Self {
384            addr,
385            width: IPV4_NET_WIDTH_MAX,
386        }
387    }
388
389    /// Return the base address used to create this subnet.
390    pub const fn addr(&self) -> Ipv4Addr {
391        self.addr
392    }
393
394    /// Return the prefix width.
395    pub const fn width(&self) -> u8 {
396        self.width
397    }
398
399    pub(crate) fn mask(&self) -> u32 {
400        Self::mask_for_width(self.width)
401    }
402
403    pub(crate) fn mask_for_width(width: u8) -> u32 {
404        u32::MAX
405            .checked_shl((IPV4_NET_WIDTH_MAX - width) as u32)
406            .unwrap_or(0)
407    }
408
409    /// Return the netmask address derived from prefix length.
410    pub fn mask_addr(&self) -> Ipv4Addr {
411        Ipv4Addr::from(self.mask())
412    }
413
414    /// Return true iff the subnet contains only the base address i.e. the
415    /// size is exactly one address.
416    pub const fn is_host_net(&self) -> bool {
417        self.width == IPV4_NET_WIDTH_MAX
418    }
419
420    /// Return `true` iff the base address corresponds to the all-zeroes host
421    /// ID in the subnet.
422    pub fn is_network_address(&self) -> bool {
423        self.addr == self.prefix()
424    }
425
426    /// Return `true` iff this subnet is in a multicast address range.
427    pub const fn is_multicast(&self) -> bool {
428        self.addr.is_multicast()
429    }
430
431    /// Return `true` iff this subnet is in an administratively scoped multicast
432    /// address range (239.0.0.0/8) as defined in [RFC 2365] and [RFC 5771].
433    ///
434    /// "Admin" is short for "administratively"; these scopes have boundaries
435    /// configured by network administrators.
436    ///
437    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
438    /// [RFC 5771]: https://tools.ietf.org/html/rfc5771
439    pub const fn is_admin_scoped_multicast(&self) -> bool {
440        // RFC 2365/RFC 5771, ยง10: The administratively scoped IPv4 multicast
441        // space is 239/8
442        // IPv4 multicast is 224.0.0.0/4, so 239/8 is a subset of that
443        self.addr.octets()[0] == 239
444    }
445
446    /// Return `true` iff this subnet is in a local multicast address range
447    /// (239.255.0.0/16) as defined in [RFC 2365]. This is the IPv4 Local
448    /// Scope.
449    ///
450    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
451    pub const fn is_local_multicast(&self) -> bool {
452        // RFC 2365: 239.255.0.0/16 is defined to be the IPv4 Local Scope
453        let octets = self.addr.octets();
454        octets[0] == 239 && octets[1] == 255
455    }
456
457    /// Return `true` iff this subnet is in an organization-local multicast
458    /// address range (239.192.0.0/14) as defined in [RFC 2365].
459    ///
460    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
461    pub const fn is_org_local_multicast(&self) -> bool {
462        // RFC 2365: The IPv4 Organization Local Scope is 239.192.0.0/14
463        // This is 239.192.0.0 - 239.195.255.255
464        let octets = self.addr.octets();
465        octets[0] == 239 && (octets[1] >= 192 && octets[1] <= 195)
466    }
467
468    /// Return `true` iff this subnet is in a loopback address range.
469    pub const fn is_loopback(&self) -> bool {
470        self.addr.is_loopback()
471    }
472
473    /// Return the number of addresses contained within this subnet or None for
474    /// a /0 subnet whose value would be one larger than can be represented in
475    /// a `u32`.
476    pub const fn size(&self) -> Option<u32> {
477        1u32.checked_shl((IPV4_NET_WIDTH_MAX - self.width) as u32)
478    }
479
480    /// Return the prefix address (the base address with the mask applied).
481    pub fn prefix(&self) -> Ipv4Addr {
482        self.first_addr()
483    }
484
485    /// Return the network address for subnets as applicable; /31 and /32
486    /// subnets return `None`.
487    pub fn network(&self) -> Option<Ipv4Addr> {
488        (self.width < 31).then(|| self.first_addr())
489    }
490
491    /// Return the broadcast address for subnets as applicable; /31 and /32
492    /// subnets return `None`.
493    pub fn broadcast(&self) -> Option<Ipv4Addr> {
494        (self.width < 31).then(|| self.last_addr())
495    }
496
497    /// Return the first address within this subnet.
498    pub fn first_addr(&self) -> Ipv4Addr {
499        let addr: u32 = self.addr.into();
500        Ipv4Addr::from(addr & self.mask())
501    }
502
503    /// Return the last address within this subnet.
504    pub fn last_addr(&self) -> Ipv4Addr {
505        let addr: u32 = self.addr.into();
506        Ipv4Addr::from(addr | !self.mask())
507    }
508
509    /// Return the first host address within this subnet. For /31 and /32
510    /// subnets that is the first address; for wider subnets this returns
511    /// the address immediately after the network address.
512    pub fn first_host(&self) -> Ipv4Addr {
513        let mask = self.mask();
514        let addr: u32 = self.addr.into();
515        let first = addr & mask;
516        if self.width == 31 || self.width == 32 {
517            Ipv4Addr::from(first)
518        } else {
519            Ipv4Addr::from(first + 1)
520        }
521    }
522
523    /// Return the last host address within this subnet. For /31 and /32
524    /// subnets that is the last address (there is no broadcast address); for
525    /// wider subnets this returns the address immediately before the broadcast
526    /// address.
527    pub fn last_host(&self) -> Ipv4Addr {
528        let mask = self.mask();
529        let addr: u32 = self.addr.into();
530        let last = addr | !mask;
531        if self.width == 31 || self.width == 32 {
532            Ipv4Addr::from(last)
533        } else {
534            Ipv4Addr::from(last - 1)
535        }
536    }
537
538    /// Return `true` iff the given IP address is within the subnet.
539    pub fn contains(&self, other: Ipv4Addr) -> bool {
540        let mask = self.mask();
541        let addr: u32 = self.addr.into();
542        let other: u32 = other.into();
543
544        (addr & mask) == (other & mask)
545    }
546
547    /// Return the nth address within this subnet or none if `n` is larger than
548    /// the size of the subnet.
549    pub fn nth(&self, n: usize) -> Option<Ipv4Addr> {
550        let addr: u32 = self.addr.into();
551        let nth = addr.checked_add(n.try_into().ok()?)?;
552        (nth <= self.last_addr().into()).then_some(nth.into())
553    }
554
555    /// Produce an iterator over all addresses within this subnet.
556    pub fn addr_iter(&self) -> impl Iterator<Item = Ipv4Addr> {
557        Ipv4NetIter {
558            next: Some(self.first_addr().into()),
559            last: self.last_addr().into(),
560        }
561    }
562
563    /// Produce an iterator over all hosts within this subnet. For /31 and /32
564    /// subnets, this is all addresses; for all larger subnets this excludes
565    /// the first (network) and last (broadcast) addresses.
566    pub fn host_iter(&self) -> impl Iterator<Item = Ipv4Addr> {
567        Ipv4NetIter {
568            next: Some(self.first_host().into()),
569            last: self.last_host().into(),
570        }
571    }
572
573    /// Returns `true` iff this subnet is wholly contained within `other`.
574    pub fn is_subnet_of(&self, other: &Self) -> bool {
575        other.first_addr() <= self.first_addr() && other.last_addr() >= self.last_addr()
576    }
577
578    /// Returns `true` iff `other` is wholly contained within this subnet.
579    pub fn is_supernet_of(&self, other: &Self) -> bool {
580        other.is_subnet_of(self)
581    }
582
583    /// Return `true` if the `other` shares any IP addresses with `self`
584    /// (e.g., `self.is_subnet_of(other)`, or vice-versa).
585    pub fn overlaps(&self, other: &Self) -> bool {
586        let (parent, child) = if self.width <= other.width {
587            (self, other)
588        } else {
589            (other, self)
590        };
591
592        child.is_subnet_of(parent)
593    }
594
595    /// Resize this subnet.
596    ///
597    /// If the new width is less than the current width the underlying address
598    /// is truncated to the new width.
599    ///
600    /// If the new width is greater than the current width the underlying
601    /// address is extended with the `fill` bits. The `fill` value is shifted
602    /// so first byte of the fill is used for the first byte of the extended
603    /// subnet. After shifting, the fill is truncated to not extend beyond the
604    /// new width. The `fill` is applied as a logical or. If the source prefix
605    /// has non-zero values in host bits and `fill` is non-zero, the result
606    /// will be the logical or of the `fill` with the host bits.
607    ///
608    /// # Examples
609    ///
610    /// Basic usage:
611    /// ```
612    /// # use oxnet::Ipv4Net;
613    /// let s16: Ipv4Net = "10.1.0.0/16".parse().unwrap();
614    /// // Extend to /24 by adding 0x02 in the third octet
615    /// let s24 = s16.resize(24, 2).unwrap();
616    /// assert_eq!(s24.to_string(), "10.1.2.0/24");
617    /// ```
618    ///
619    /// Non-zero values in host-bits:
620    /// ```
621    /// # use oxnet::Ipv4Net;
622    /// let s16: Ipv4Net = "10.1.2.3/16".parse().unwrap();
623    /// let s24 = s16.resize(24, 0).unwrap();
624    /// assert_eq!(s24, "10.1.2.3/24".parse().unwrap());
625    /// let s24 = s16.resize(24, 255).unwrap();
626    /// assert_eq!(s24, "10.1.255.3/24".parse().unwrap());
627    /// ```
628    pub fn resize(&self, width: u8, fill: u32) -> Result<Self, IpNetPrefixError> {
629        if width > IPV4_NET_WIDTH_MAX {
630            return Err(IpNetPrefixError(width));
631        }
632        if width < self.width {
633            Ok(Self {
634                addr: Ipv4Addr::from(u32::from(self.addr) & Self::mask_for_width(width)),
635                width,
636            })
637        } else if width == self.width {
638            Ok(*self)
639        } else {
640            let fill = (fill << (IPV4_NET_WIDTH_MAX - width)) & Self::mask_for_width(width);
641            Ok(Self {
642                addr: Ipv4Addr::from(u32::from(self.addr) | fill),
643                width,
644            })
645        }
646    }
647}
648
649impl std::fmt::Display for Ipv4Net {
650    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
651        write!(f, "{}/{}", &self.addr, self.width)
652    }
653}
654
655impl std::str::FromStr for Ipv4Net {
656    type Err = IpNetParseError;
657
658    fn from_str(s: &str) -> Result<Self, Self::Err> {
659        let Some((addr_str, prefix_str)) = s.split_once('/') else {
660            return Err(IpNetParseError::NoPrefix);
661        };
662
663        let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
664        let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
665        Ipv4Net::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
666    }
667}
668
669#[cfg(feature = "serde")]
670impl<'de> serde::Deserialize<'de> for Ipv4Net {
671    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
672    where
673        D: serde::Deserializer<'de>,
674    {
675        String::deserialize(deserializer)?
676            .parse()
677            .map_err(<D::Error as serde::de::Error>::custom)
678    }
679}
680
681#[cfg(feature = "serde")]
682impl serde::Serialize for Ipv4Net {
683    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
684    where
685        S: serde::Serializer,
686    {
687        serializer.serialize_str(&format!("{self}"))
688    }
689}
690
691#[cfg(feature = "schemars")]
692const IPV4_NET_REGEX: &str = concat!(
693    r#"^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}"#,
694    r#"([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"#,
695    r#"/([0-9]|1[0-9]|2[0-9]|3[0-2])$"#,
696);
697
698#[cfg(feature = "schemars")]
699impl schemars::JsonSchema for Ipv4Net {
700    fn schema_name() -> String {
701        "Ipv4Net".to_string()
702    }
703
704    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
705        schemars::schema::SchemaObject {
706            metadata: Some(Box::new(schemars::schema::Metadata {
707                title: Some("An IPv4 subnet".to_string()),
708                description: Some("An IPv4 subnet, including prefix and prefix length".to_string()),
709                examples: vec!["192.168.1.0/24".into()],
710                ..Default::default()
711            })),
712            instance_type: Some(schemars::schema::InstanceType::String.into()),
713            string: Some(Box::new(schemars::schema::StringValidation {
714                pattern: Some(IPV4_NET_REGEX.to_string()),
715                ..Default::default()
716            })),
717            extensions: crate::schema_util::extension("Ipv4Net", "0.1.0"),
718            ..Default::default()
719        }
720        .into()
721    }
722}
723
724/// The highest value for an IPv6 subnet prefix
725pub const IPV6_NET_WIDTH_MAX: u8 = 128;
726
727/// IPv6 multicast scope values as defined in [RFC 4291] and [RFC 7346].
728///
729/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
730/// [RFC 7346]: https://tools.ietf.org/html/rfc7346
731#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
732#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
733pub enum MulticastScopeV6 {
734    /// Interface-local scope (0x1)
735    InterfaceLocal = 0x1,
736    /// Link-local scope (0x2)
737    LinkLocal = 0x2,
738    /// Admin-local scope (0x4) - administratively configured
739    AdminLocal = 0x4,
740    /// Site-local scope (0x5) - administratively configured
741    SiteLocal = 0x5,
742    /// Organization-local scope (0x8) - administratively configured
743    OrganizationLocal = 0x8,
744    /// Global scope (0xE)
745    Global = 0xE,
746}
747
748impl MulticastScopeV6 {
749    /// Returns `true` if this scope is administratively configured
750    /// (scopes 4, 5, 8).
751    pub const fn is_admin_scoped_multicast(&self) -> bool {
752        matches!(
753            self,
754            MulticastScopeV6::AdminLocal
755                | MulticastScopeV6::SiteLocal
756                | MulticastScopeV6::OrganizationLocal
757        )
758    }
759
760    /// Create a `MulticastScopeV6` from a raw scope value.
761    /// Returns `None` if the scope value is not recognized.
762    pub const fn from_u8(scope: u8) -> Option<Self> {
763        match scope {
764            0x1 => Some(MulticastScopeV6::InterfaceLocal),
765            0x2 => Some(MulticastScopeV6::LinkLocal),
766            0x4 => Some(MulticastScopeV6::AdminLocal),
767            0x5 => Some(MulticastScopeV6::SiteLocal),
768            0x8 => Some(MulticastScopeV6::OrganizationLocal),
769            0xE => Some(MulticastScopeV6::Global),
770            _ => None,
771        }
772    }
773}
774
775/// An IPv6 subnet
776#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
777pub struct Ipv6Net {
778    addr: Ipv6Addr,
779    width: u8,
780}
781
782impl Ipv6Net {
783    /// Create an Ipv6Net with the given base address and prefix width.
784    pub fn new(addr: Ipv6Addr, width: u8) -> Result<Self, IpNetPrefixError> {
785        if width > IPV6_NET_WIDTH_MAX {
786            Err(IpNetPrefixError(width))
787        } else {
788            Ok(Self { addr, width })
789        }
790    }
791
792    /// Create an Ipv6Net with the given address and prefix width with no
793    /// checks for the validity of the prefix length.
794    pub const fn new_unchecked(addr: Ipv6Addr, width: u8) -> Self {
795        Self { addr, width }
796    }
797
798    /// Create an Ipv6Net that contains *exclusively* the given address.
799    pub const fn host_net(addr: Ipv6Addr) -> Self {
800        Self {
801            addr,
802            width: IPV6_NET_WIDTH_MAX,
803        }
804    }
805
806    /// Return the base address used to create this subnet.
807    pub const fn addr(&self) -> Ipv6Addr {
808        self.addr
809    }
810
811    /// Return the prefix width.
812    pub const fn width(&self) -> u8 {
813        self.width
814    }
815
816    pub(crate) fn mask(&self) -> u128 {
817        Self::mask_for_width(self.width)
818    }
819
820    pub(crate) fn mask_for_width(width: u8) -> u128 {
821        u128::MAX
822            .checked_shl((IPV6_NET_WIDTH_MAX - width) as u32)
823            .unwrap_or(0)
824    }
825
826    /// Return the netmask address derived from prefix length.
827    pub fn mask_addr(&self) -> Ipv6Addr {
828        Ipv6Addr::from(self.mask())
829    }
830
831    /// Return true iff the subnet contains only the base address i.e. the
832    /// size is exactly one address.
833    pub const fn is_host_net(&self) -> bool {
834        self.width == IPV6_NET_WIDTH_MAX
835    }
836
837    /// Return `true` iff the base address corresponds to the all-zeroes host
838    /// ID in the subnet.
839    pub fn is_network_address(&self) -> bool {
840        self.addr == self.prefix()
841    }
842
843    /// Return `true` iff this subnet is in a multicast address range.
844    pub const fn is_multicast(&self) -> bool {
845        self.addr.is_multicast()
846    }
847
848    /// Return the IPv6 multicast scope if this subnet is a multicast address,
849    /// or `None` otherwise. This extracts the scope field from the multicast
850    /// address as defined in [RFC 4291] and [RFC 7346].
851    ///
852    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
853    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
854    pub const fn multicast_scope(&self) -> Option<MulticastScopeV6> {
855        if !self.addr.is_multicast() {
856            return None;
857        }
858
859        // Extract the scope field (bits 4-7 of the second byte)
860        let segments = self.addr.segments();
861        let scope = (segments[0] & 0x000F) as u8;
862
863        MulticastScopeV6::from_u8(scope)
864    }
865
866    /// Return `true` iff this subnet is in an administratively scoped
867    /// multicast address range with boundaries that are administratively
868    /// configured.
869    ///
870    /// "Admin" is short for "administratively"; these scopes have boundaries
871    /// configured by network administrators, unlike well-known scopes like
872    /// link-local or global.
873    ///
874    /// For IPv6, this includes scopes 4, 5, and 8 (admin-local, site-local,
875    /// organization-local) as defined in [RFC 7346] and [RFC 4291].
876    ///
877    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
878    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
879    pub const fn is_admin_scoped_multicast(&self) -> bool {
880        match self.multicast_scope() {
881            Some(scope) => scope.is_admin_scoped_multicast(),
882            None => false,
883        }
884    }
885
886    /// Return `true` iff this address is an admin-local multicast address
887    /// (scope 4) as defined in [RFC 7346] and [RFC 4291].
888    ///
889    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
890    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
891    pub const fn is_admin_local_multicast(&self) -> bool {
892        matches!(self.multicast_scope(), Some(MulticastScopeV6::AdminLocal))
893    }
894
895    /// Return `true` iff this address is a site-local multicast address
896    /// (scope 5) as defined in [RFC 7346] and [RFC 4291].
897    ///
898    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
899    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
900    pub const fn is_site_local_multicast(&self) -> bool {
901        matches!(self.multicast_scope(), Some(MulticastScopeV6::SiteLocal))
902    }
903
904    /// Return `true` iff this address is an organization-local multicast
905    /// address (scope 8) as defined in [RFC 7346] and [RFC 4291].
906    ///
907    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
908    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
909    pub const fn is_org_local_multicast(&self) -> bool {
910        matches!(
911            self.multicast_scope(),
912            Some(MulticastScopeV6::OrganizationLocal)
913        )
914    }
915
916    /// Return `true` iff this subnet is in a loopback address range.
917    pub const fn is_loopback(&self) -> bool {
918        self.addr.is_loopback()
919    }
920
921    /// Return the number of addresses contained within this subnet or None for
922    /// a /0 subnet whose value would be one larger than can be represented in
923    /// a `u128`.
924    pub const fn size(&self) -> Option<u128> {
925        1u128.checked_shl((IPV6_NET_WIDTH_MAX - self.width) as u32)
926    }
927
928    /// Return the prefix address (the base address with the mask applied).
929    pub fn prefix(&self) -> Ipv6Addr {
930        self.first_addr()
931    }
932
933    /// Return `true` if this subnetwork is in the IPv6 Unique Local Address
934    /// range defined in [RFC 4193], e.g., `fd00:/8`.
935    ///
936    /// [RFC 4193]: https://tools.ietf.org/html/rfc4193
937    pub const fn is_unique_local(&self) -> bool {
938        self.addr.is_unique_local()
939    }
940
941    /// Return the first address within this subnet.
942    pub fn first_addr(&self) -> Ipv6Addr {
943        let addr: u128 = self.addr.into();
944        Ipv6Addr::from(addr & self.mask())
945    }
946
947    /// The broadcast address for this subnet which is also the last address.
948    pub fn last_addr(&self) -> Ipv6Addr {
949        let addr: u128 = self.addr.into();
950        Ipv6Addr::from(addr | !self.mask())
951    }
952
953    /// Return an interator over the addresses of this subnet.
954    pub fn iter(&self) -> impl Iterator<Item = Ipv6Addr> {
955        Ipv6NetIter {
956            next: Some(self.first_addr().into()),
957            last: self.last_addr().into(),
958        }
959    }
960
961    /// Return `true` if the address is within the subnet.
962    pub fn contains(&self, other: Ipv6Addr) -> bool {
963        let mask = self.mask();
964        let addr: u128 = self.addr.into();
965        let other: u128 = other.into();
966
967        (addr & mask) == (other & mask)
968    }
969
970    /// Return the nth address within this subnet or none if `n` is larger than
971    /// the size of the subnet.
972    pub fn nth(&self, n: u128) -> Option<Ipv6Addr> {
973        let addr: u128 = self.addr.into();
974        let nth = addr.checked_add(n)?;
975        (nth <= self.last_addr().into()).then_some(nth.into())
976    }
977
978    /// Returns `true` iff this subnet is wholly contained within `other`.
979    pub fn is_subnet_of(&self, other: &Self) -> bool {
980        other.first_addr() <= self.first_addr() && other.last_addr() >= self.last_addr()
981    }
982
983    /// Returns `true` iff `other` is wholly contained within this subnet.
984    pub fn is_supernet_of(&self, other: &Self) -> bool {
985        other.is_subnet_of(self)
986    }
987
988    /// Return `true` if the `other` shares any IP addresses with `self`
989    /// (e.g., `self.is_subnet_of(other)`, or vice-versa).
990    pub fn overlaps(&self, other: &Self) -> bool {
991        let (parent, child) = if self.width <= other.width {
992            (self, other)
993        } else {
994            (other, self)
995        };
996
997        child.is_subnet_of(parent)
998    }
999
1000    /// Resize this subnet.
1001    ///
1002    /// If the new width is less than the current width the underlying address
1003    /// is truncated to the new width.
1004    ///
1005    /// If the new width is greater than the current width the underlying
1006    /// address is extended with the `fill` bits. The `fill` value is shifted
1007    /// so first byte of the fill is used for the first byte of the extended
1008    /// subnet. After shifting, the fill is truncated to not extend beyond the
1009    /// new width. The `fill` is applied as a logical or. If the source prefix
1010    /// has non-zero values in host bits and `fill` is non-zero, the result
1011    /// will be the logical or of the `fill` with the host bits.
1012    ///
1013    /// # Examples
1014    /// Basic usage:
1015    /// ```
1016    /// # use oxnet::Ipv6Net;
1017    /// let s56: Ipv6Net = "fd00:a:b:cc00::/56".parse().unwrap();
1018    /// // Extend a /56 to a /64
1019    /// let s64 = s56.resize(64, 0xdd).unwrap();
1020    /// assert_eq!(s64, "fd00:a:b:ccdd::/64".parse().unwrap());
1021    /// ```
1022    ///
1023    /// Non-zero values in host-bits:
1024    /// ```
1025    /// # use oxnet::Ipv6Net;
1026    /// let s56: Ipv6Net = "fd00:a:b:ccdd::/56".parse().unwrap();
1027    /// let s64 = s56.resize(64, 0).unwrap();
1028    /// assert_eq!(s64, "fd00:a:b:ccdd::/64".parse().unwrap());
1029    /// let s64 = s56.resize(64, 0xff).unwrap();
1030    /// assert_eq!(s64, "fd00:a:b:ccff::/64".parse().unwrap());
1031    /// ```
1032    pub fn resize(&self, width: u8, fill: u128) -> Result<Self, IpNetPrefixError> {
1033        if width > IPV6_NET_WIDTH_MAX {
1034            return Err(IpNetPrefixError(width));
1035        }
1036        if width < self.width {
1037            Ok(Self {
1038                addr: Ipv6Addr::from(u128::from(self.addr) & Self::mask_for_width(width)),
1039                width,
1040            })
1041        } else if width == self.width {
1042            Ok(*self)
1043        } else {
1044            let fill = (fill << (IPV6_NET_WIDTH_MAX - width)) & Self::mask_for_width(width);
1045            Ok(Self {
1046                addr: Ipv6Addr::from(u128::from(self.addr) | fill),
1047                width,
1048            })
1049        }
1050    }
1051}
1052
1053impl std::fmt::Display for Ipv6Net {
1054    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1055        write!(f, "{}/{}", &self.addr, self.width)
1056    }
1057}
1058
1059impl std::str::FromStr for Ipv6Net {
1060    type Err = IpNetParseError;
1061
1062    fn from_str(s: &str) -> Result<Self, Self::Err> {
1063        let Some((addr_str, prefix_str)) = s.split_once('/') else {
1064            return Err(IpNetParseError::NoPrefix);
1065        };
1066
1067        let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
1068        let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
1069        Ipv6Net::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
1070    }
1071}
1072
1073#[cfg(feature = "serde")]
1074impl<'de> serde::Deserialize<'de> for Ipv6Net {
1075    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1076    where
1077        D: serde::Deserializer<'de>,
1078    {
1079        String::deserialize(deserializer)?
1080            .parse()
1081            .map_err(<D::Error as serde::de::Error>::custom)
1082    }
1083}
1084
1085#[cfg(feature = "serde")]
1086impl serde::Serialize for Ipv6Net {
1087    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1088    where
1089        S: serde::Serializer,
1090    {
1091        serializer.serialize_str(&format!("{self}"))
1092    }
1093}
1094
1095#[cfg(feature = "schemars")]
1096const IPV6_NET_REGEX: &str = concat!(
1097    r#"^("#,
1098    r#"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"#,
1099    r#"([0-9a-fA-F]{1,4}:){1,7}:|"#,
1100    r#"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"#,
1101    r#"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"#,
1102    r#"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"#,
1103    r#"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"#,
1104    r#"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"#,
1105    r#"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"#,
1106    r#":((:[0-9a-fA-F]{1,4}){1,7}|:)|"#,
1107    r#"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"#,
1108    r#"::(ffff(:0{1,4}){0,1}:){0,1}"#,
1109    r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#,
1110    r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"#,
1111    r#"([0-9a-fA-F]{1,4}:){1,4}:"#,
1112    r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#,
1113    r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#,
1114    r#")\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$"#,
1115);
1116
1117#[cfg(feature = "schemars")]
1118impl schemars::JsonSchema for Ipv6Net {
1119    fn schema_name() -> String {
1120        "Ipv6Net".to_string()
1121    }
1122
1123    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1124        schemars::schema::SchemaObject {
1125            metadata: Some(Box::new(schemars::schema::Metadata {
1126                title: Some("An IPv6 subnet".to_string()),
1127                description: Some("An IPv6 subnet, including prefix and subnet mask".to_string()),
1128                examples: vec!["fd12:3456::/64".into()],
1129                ..Default::default()
1130            })),
1131            instance_type: Some(schemars::schema::InstanceType::String.into()),
1132            string: Some(Box::new(schemars::schema::StringValidation {
1133                pattern: Some(IPV6_NET_REGEX.to_string()),
1134                ..Default::default()
1135            })),
1136            extensions: crate::schema_util::extension("Ipv6Net", "0.1.0"),
1137            ..Default::default()
1138        }
1139        .into()
1140    }
1141}
1142
1143pub struct Ipv4NetIter {
1144    next: Option<u32>,
1145    last: u32,
1146}
1147
1148impl Iterator for Ipv4NetIter {
1149    type Item = Ipv4Addr;
1150
1151    fn next(&mut self) -> Option<Self::Item> {
1152        let next = self.next?;
1153        if next == self.last {
1154            self.next = None;
1155        } else {
1156            self.next = Some(next + 1)
1157        }
1158        Some(next.into())
1159    }
1160
1161    fn nth(&mut self, n: usize) -> Option<Self::Item> {
1162        let next = self.next?;
1163        let nth = next.checked_add(n as u32)?;
1164        self.next = (nth <= self.last).then_some(nth);
1165        self.next()
1166    }
1167}
1168
1169pub struct Ipv6NetIter {
1170    next: Option<u128>,
1171    last: u128,
1172}
1173
1174impl Iterator for Ipv6NetIter {
1175    type Item = Ipv6Addr;
1176
1177    fn next(&mut self) -> Option<Self::Item> {
1178        let next = self.next?;
1179        if next == self.last {
1180            self.next = None;
1181        } else {
1182            self.next = Some(next + 1)
1183        }
1184        Some(next.into())
1185    }
1186
1187    fn nth(&mut self, n: usize) -> Option<Self::Item> {
1188        let next = self.next?;
1189        let nth = next.checked_add(n as u128)?;
1190        self.next = (nth <= self.last).then_some(nth);
1191        self.next()
1192    }
1193}
1194
1195/// Error conditions that can arise from [UlaBuilder::build]
1196#[cfg(feature = "ula")]
1197#[derive(Debug, Clone)]
1198pub enum UlaBuildError {
1199    /// An error occurred using a user specified time to build a ULA
1200    Time(SystemTimeError),
1201    /// An error occurred constructing the built prefix
1202    Prefix(IpNetPrefixError),
1203}
1204
1205#[cfg(feature = "ula")]
1206impl std::fmt::Display for UlaBuildError {
1207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1208        match self {
1209            Self::Time(e) => write!(f, "invalid time provided: {e}"),
1210            Self::Prefix(e) => write!(f, "unable to construct ULA prefix: {e}"),
1211        }
1212    }
1213}
1214
1215#[cfg(feature = "ula")]
1216impl std::error::Error for UlaBuildError {}
1217
1218#[cfg(feature = "ula")]
1219impl From<SystemTimeError> for UlaBuildError {
1220    fn from(value: SystemTimeError) -> Self {
1221        Self::Time(value)
1222    }
1223}
1224
1225#[cfg(feature = "ula")]
1226impl From<IpNetPrefixError> for UlaBuildError {
1227    fn from(value: IpNetPrefixError) -> Self {
1228        Self::Prefix(value)
1229    }
1230}
1231
1232/// Build an IPv6 unique local address that conforms to RFC 4193.
1233#[cfg(feature = "ula")]
1234#[derive(Default)]
1235pub struct UlaBuilder {
1236    date: Option<SystemTime>,
1237    id: Option<Vec<u8>>,
1238}
1239
1240#[cfg(feature = "ula")]
1241impl UlaBuilder {
1242    /// Set the ULA id.
1243    pub fn id(&mut self, id: impl AsRef<[u8]>) -> &mut Self {
1244        self.id = Some(id.as_ref().to_vec());
1245        self
1246    }
1247
1248    /// Set the ULA generation date.
1249    pub fn date(&mut self, date: SystemTime) -> &mut Self {
1250        self.date = Some(date);
1251        self
1252    }
1253
1254    /// Produce the Ipv6Net from the builder.
1255    ///
1256    /// This will produce a /48 with `fd` as the leading 8 bits followed by 40
1257    /// random bits that are determined according to the algorithm in RFC 4193
1258    /// section 3.2.2. Subsequent modification such as resizing to a /56 or /64
1259    /// can be accomplished with `[Ipv6Net::resize]`.
1260    pub fn build(&self) -> Result<Ipv6Net, UlaBuildError> {
1261        use sha1::{Digest, Sha1};
1262        use std::time::SystemTime;
1263
1264        // Get or generate ID (8 bytes)
1265        let id: Vec<u8> = self.id.clone().unwrap_or_else(|| {
1266            let mut rng = rand::rng();
1267            rng.random::<[u8; 8]>().to_vec()
1268        });
1269
1270        // Get time and convert to NTP format
1271        let time = self.date.unwrap_or_else(SystemTime::now);
1272        let ntp_time = system_time_to_ntp(time)?;
1273
1274        // Hash the inputs per RFC 4193
1275        let mut hasher = Sha1::new();
1276        hasher.update(ntp_time.to_be_bytes());
1277        hasher.update(&id);
1278        let hash = hasher.finalize();
1279
1280        // Extract 40 bits (5 bytes) for Global ID
1281        let global_id = &hash[..5];
1282
1283        // Build the fd00::/48 prefix
1284        // Format: fd + 40-bit Global ID + 16-bit Subnet ID (0 for /48)
1285        let addr = Ipv6Addr::new(
1286            0xfd00 | (global_id[0] as u16),
1287            u16::from_be_bytes([global_id[1], global_id[2]]),
1288            u16::from_be_bytes([global_id[3], global_id[4]]),
1289            0,
1290            0,
1291            0,
1292            0,
1293            0,
1294        );
1295
1296        Ok(Ipv6Net::new(addr, 48)?)
1297    }
1298}
1299
1300#[cfg(feature = "ula")]
1301fn system_time_to_ntp(time: SystemTime) -> Result<u64, SystemTimeError> {
1302    use std::time::UNIX_EPOCH;
1303
1304    // NTP epoch is 1900-01-01 00:00:00 UTC
1305    // Unix epoch is 1970-01-01 00:00:00 UTC
1306    // Difference is 70 years = 2208988800 seconds
1307    const NTP_UNIX_OFFSET: u64 = 2208988800;
1308
1309    let duration = time.duration_since(UNIX_EPOCH)?;
1310    let secs = duration.as_secs() + NTP_UNIX_OFFSET;
1311    let frac = ((duration.subsec_nanos() as u64) << 32) / 1_000_000_000;
1312
1313    Ok((secs << 32) | frac)
1314}
1315
1316#[cfg(feature = "ipnetwork")]
1317mod ipnetwork_feature {
1318    use super::*;
1319    use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
1320
1321    impl From<IpNetwork> for IpNet {
1322        fn from(value: IpNetwork) -> Self {
1323            match value {
1324                IpNetwork::V4(net) => Self::V4(net.into()),
1325                IpNetwork::V6(net) => Self::V6(net.into()),
1326            }
1327        }
1328    }
1329
1330    impl From<IpNet> for IpNetwork {
1331        fn from(value: IpNet) -> Self {
1332            match value {
1333                IpNet::V4(net) => Self::V4(net.into()),
1334                IpNet::V6(net) => Self::V6(net.into()),
1335            }
1336        }
1337    }
1338
1339    impl From<Ipv4Network> for Ipv4Net {
1340        fn from(value: Ipv4Network) -> Self {
1341            Self {
1342                addr: value.ip(),
1343                width: value.prefix(),
1344            }
1345        }
1346    }
1347
1348    impl From<Ipv4Net> for Ipv4Network {
1349        fn from(value: Ipv4Net) -> Self {
1350            Self::new(value.addr, value.width).unwrap()
1351        }
1352    }
1353
1354    impl From<Ipv6Network> for Ipv6Net {
1355        fn from(value: Ipv6Network) -> Self {
1356            Self {
1357                addr: value.ip(),
1358                width: value.prefix(),
1359            }
1360        }
1361    }
1362
1363    impl From<Ipv6Net> for Ipv6Network {
1364        fn from(value: Ipv6Net) -> Self {
1365            Self::new(value.addr, value.width).unwrap()
1366        }
1367    }
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372    use super::*;
1373
1374    #[test]
1375    fn test_ipv6_regex() {
1376        let re = regress::Regex::new(IPV6_NET_REGEX).unwrap();
1377        for case in [
1378            "1:2:3:4:5:6:7:8",
1379            "1:a:2:b:3:c:4:d",
1380            "1::",
1381            "::1",
1382            "::",
1383            "1::3:4:5:6:7:8",
1384            "1:2::4:5:6:7:8",
1385            "1:2:3::5:6:7:8",
1386            "1:2:3:4::6:7:8",
1387            "1:2:3:4:5::7:8",
1388            "1:2:3:4:5:6::8",
1389            "1:2:3:4:5:6:7::",
1390            "2001::",
1391            "fd00::",
1392            "::100:1",
1393            "fd12:3456::",
1394        ] {
1395            for prefix in 0..=128 {
1396                let net = format!("{case}/{prefix}");
1397                assert!(
1398                    re.find(&net).is_some(),
1399                    "Expected to match IPv6 case: {prefix}",
1400                );
1401            }
1402        }
1403    }
1404
1405    #[test]
1406    fn test_ipv4_net_operations() {
1407        let x: IpNet = "0.0.0.0/0".parse().unwrap();
1408        assert_eq!(x, IpNet::V4("0.0.0.0/0".parse().unwrap()));
1409    }
1410
1411    #[test]
1412    fn test_ipnet_serde() {
1413        let net_str = "fd00:2::/32";
1414        let net: IpNet = net_str.parse().unwrap();
1415        let ser = serde_json::to_string(&net).unwrap();
1416
1417        assert_eq!(format!(r#""{net_str}""#), ser);
1418        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1419        assert_eq!(net, net_des);
1420
1421        let net_str = "fd00:47::1/64";
1422        let net: IpNet = net_str.parse().unwrap();
1423        let ser = serde_json::to_string(&net).unwrap();
1424
1425        assert_eq!(format!(r#""{net_str}""#), ser);
1426        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1427        assert_eq!(net, net_des);
1428
1429        let net_str = "192.168.1.1/16";
1430        let net: IpNet = net_str.parse().unwrap();
1431        let ser = serde_json::to_string(&net).unwrap();
1432
1433        assert_eq!(format!(r#""{net_str}""#), ser);
1434        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1435        assert_eq!(net, net_des);
1436
1437        let net_str = "0.0.0.0/0";
1438        let net: IpNet = net_str.parse().unwrap();
1439        let ser = serde_json::to_string(&net).unwrap();
1440
1441        assert_eq!(format!(r#""{net_str}""#), ser);
1442        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1443        assert_eq!(net, net_des);
1444    }
1445
1446    #[test]
1447    fn test_ipnet_size() {
1448        let net = Ipv4Net::host_net("1.2.3.4".parse().unwrap());
1449        assert_eq!(net.size(), Some(1));
1450        assert_eq!(net.width(), 32);
1451        assert_eq!(net.mask(), 0xffff_ffff);
1452        assert_eq!(net.mask_addr(), Ipv4Addr::new(0xff, 0xff, 0xff, 0xff));
1453
1454        let net = Ipv4Net::new("1.2.3.4".parse().unwrap(), 24).unwrap();
1455        assert_eq!(net.size(), Some(256));
1456        assert_eq!(net.width(), 24);
1457        assert_eq!(net.mask(), 0xffff_ff00);
1458        assert_eq!(net.mask_addr(), Ipv4Addr::new(0xff, 0xff, 0xff, 0));
1459
1460        let net = Ipv4Net::new("0.0.0.0".parse().unwrap(), 0).unwrap();
1461        assert_eq!(net.size(), None);
1462        assert_eq!(net.width(), 0);
1463        assert_eq!(net.mask(), 0);
1464        assert_eq!(net.mask_addr(), Ipv4Addr::new(0, 0, 0, 0));
1465
1466        let net = Ipv6Net::host_net("fd00:47::1".parse().unwrap());
1467        assert_eq!(net.size(), Some(1));
1468        assert_eq!(net.width(), 128);
1469        assert_eq!(net.mask(), 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff);
1470        assert_eq!(
1471            net.mask_addr(),
1472            Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)
1473        );
1474
1475        let net = Ipv6Net::new("fd00:47::1".parse().unwrap(), 56).unwrap();
1476        assert_eq!(net.size(), Some(0x0000_0000_0000_0100_0000_0000_0000_0000));
1477        assert_eq!(net.width(), 56);
1478        assert_eq!(net.mask(), 0xffff_ffff_ffff_ff00_0000_0000_0000_0000);
1479        assert_eq!(
1480            net.mask_addr(),
1481            Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xff00, 0, 0, 0, 0)
1482        );
1483    }
1484
1485    #[test]
1486    fn test_iter() {
1487        let ipnet = Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0).unwrap();
1488
1489        let actual = ipnet.addr_iter().take(5).collect::<Vec<_>>();
1490        let expected = (0..5).map(Ipv4Addr::from).collect::<Vec<_>>();
1491        assert_eq!(actual, expected);
1492
1493        let actual = ipnet.addr_iter().skip(5).take(10).collect::<Vec<_>>();
1494        let expected = (5..15).map(Ipv4Addr::from).collect::<Vec<_>>();
1495        assert_eq!(actual, expected);
1496
1497        let ipnet = Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0).unwrap();
1498
1499        let actual = ipnet.iter().take(5).collect::<Vec<_>>();
1500        let expected = (0..5).map(Ipv6Addr::from).collect::<Vec<_>>();
1501        assert_eq!(actual, expected);
1502
1503        let actual = ipnet.iter().skip(5).take(10).collect::<Vec<_>>();
1504        let expected = (5..15).map(Ipv6Addr::from).collect::<Vec<_>>();
1505        assert_eq!(actual, expected);
1506    }
1507
1508    #[test]
1509    fn test_contains() {
1510        let default_v4: IpNet = "0.0.0.0/0".parse().unwrap();
1511        let private_v4: IpNet = "10.0.0.0/8".parse().unwrap();
1512        let privater_v4_c0: IpNet = "10.0.0.0/9".parse().unwrap();
1513        let privater_v4_c1: IpNet = "10.128.0.0/9".parse().unwrap();
1514
1515        assert!(private_v4.is_subnet_of(&default_v4));
1516        assert!(privater_v4_c0.is_subnet_of(&default_v4));
1517        assert!(privater_v4_c0.is_subnet_of(&private_v4));
1518        assert!(privater_v4_c1.is_subnet_of(&default_v4));
1519        assert!(privater_v4_c1.is_subnet_of(&private_v4));
1520
1521        assert!(private_v4.is_supernet_of(&privater_v4_c0));
1522        assert!(private_v4.is_supernet_of(&privater_v4_c1));
1523
1524        assert!(!privater_v4_c0.overlaps(&privater_v4_c1));
1525        assert!(!privater_v4_c1.overlaps(&privater_v4_c0));
1526        assert!(privater_v4_c0.overlaps(&privater_v4_c0));
1527        assert!(privater_v4_c0.overlaps(&private_v4));
1528        assert!(private_v4.overlaps(&privater_v4_c0));
1529
1530        let child_ip: IpNet = "10.128.20.20/16".parse().unwrap();
1531        assert!(child_ip.is_subnet_of(&privater_v4_c1));
1532        assert!(!child_ip.is_subnet_of(&privater_v4_c0));
1533    }
1534
1535    #[test]
1536    fn test_is_network_addr() {
1537        let v4_net: IpNet = "127.0.0.0/8".parse().unwrap();
1538        let v4_host: IpNet = "127.0.0.1/8".parse().unwrap();
1539        let v6_net: IpNet = "fd00:1234:5678::/48".parse().unwrap();
1540        let v6_host: IpNet = "fd00:1234:5678::7777/48".parse().unwrap();
1541
1542        assert!(v4_net.is_network_address());
1543        assert!(!v4_host.is_network_address());
1544        assert!(v6_net.is_network_address());
1545        assert!(!v6_host.is_network_address());
1546
1547        // We don't return a `.network()` for a /31 or /32, but the host bits
1548        // are zero in these addresses (i.e., they're in a canonical form).
1549        let two_addr: IpNet = "10.7.7.64/31".parse().unwrap();
1550        let one_addr: IpNet = "10.7.7.64/32".parse().unwrap();
1551        assert!(two_addr.is_network_address());
1552        assert!(one_addr.is_network_address());
1553
1554        // The IpNet as used in a default route should be considered valid in
1555        // this form.
1556        let unspec: IpNet = "0.0.0.0/0".parse().unwrap();
1557        assert!(unspec.is_network_address());
1558    }
1559
1560    #[test]
1561    fn test_is_multicast_with_scopes() {
1562        // IPv4 multicast tests (224.0.0.0/4 is the IPv4 multicast range)
1563        let v4_mcast: IpNet = "224.0.0.1/32".parse().unwrap();
1564        let v4_not_mcast: IpNet = "192.168.1.1/24".parse().unwrap();
1565
1566        assert!(v4_mcast.is_multicast());
1567        assert!(!v4_not_mcast.is_multicast());
1568
1569        // IPv6 multicast tests (ff00::/8 is the IPv6 multicast range)
1570        let v6_mcast: IpNet = "ff02::1/128".parse().unwrap();
1571        let v6_not_mcast: IpNet = "2001:db8::1/64".parse().unwrap();
1572
1573        assert!(v6_mcast.is_multicast());
1574        assert!(!v6_not_mcast.is_multicast());
1575
1576        // Test for site-local multicast (scope 5)
1577        let v6_site_local_mcast: IpNet = "ff05::1/128".parse().unwrap();
1578        // Test for organization-local multicast (scope 8)
1579        let v6_org_local_mcast: IpNet = "ff08::1/128".parse().unwrap();
1580        // Test for admin-local multicast (scope 4)
1581        let v6_admin_local_mcast: IpNet = "ff04::1/128".parse().unwrap();
1582        // Test for a multicast address that is not admin scoped (link-local, scope 2)
1583        let v6_link_local_mcast: IpNet = "ff02::1/128".parse().unwrap();
1584
1585        // Test admin scoped multicast (covers scopes 4, 5, 8 for IPv6, 239/8 for IPv4)
1586        assert!(v6_admin_local_mcast.is_admin_scoped_multicast());
1587        assert!(v6_site_local_mcast.is_admin_scoped_multicast());
1588        assert!(v6_org_local_mcast.is_admin_scoped_multicast());
1589        assert!(!v6_link_local_mcast.is_admin_scoped_multicast()); // scope 2 is not administratively configured
1590        assert!(!v6_not_mcast.is_admin_scoped_multicast());
1591
1592        // Test IPv4 admin scoped (239.0.0.0/8)
1593        let v4_admin_scoped: IpNet = "239.0.0.1/32".parse().unwrap();
1594        let v4_admin_scoped_range: IpNet = "239.192.0.0/16".parse().unwrap();
1595        assert!(v4_admin_scoped.is_admin_scoped_multicast());
1596        assert!(v4_admin_scoped_range.is_admin_scoped_multicast());
1597        assert!(!v4_mcast.is_admin_scoped_multicast());
1598
1599        // Test IPv6 admin-local multicast (scope 4) - IPv4 does not have this
1600        assert!(!v6_site_local_mcast.is_admin_local_multicast());
1601        assert!(!v6_org_local_mcast.is_admin_local_multicast());
1602        assert!(v6_admin_local_mcast.is_admin_local_multicast());
1603        assert!(!v6_link_local_mcast.is_admin_local_multicast());
1604        assert!(!v6_not_mcast.is_admin_local_multicast());
1605        assert!(!v4_mcast.is_admin_local_multicast()); // IPv4 does not have admin-local
1606        assert!(!v4_admin_scoped.is_admin_local_multicast()); // IPv4 does not have admin-local
1607
1608        // Test IPv4 local multicast (239.255.0.0/16) - IPv6 does not have this
1609        let v4_local_mcast: IpNet = "239.255.0.1/32".parse().unwrap();
1610        let v4_local_mcast_range: IpNet = "239.255.128.0/24".parse().unwrap();
1611        let v4_not_local: IpNet = "239.254.255.255/32".parse().unwrap();
1612        assert!(v4_local_mcast.is_local_multicast());
1613        assert!(v4_local_mcast_range.is_local_multicast());
1614        assert!(!v4_not_local.is_local_multicast());
1615        assert!(!v4_mcast.is_local_multicast()); // 224.0.0.1 is not in 239.255/16
1616        assert!(!v6_admin_local_mcast.is_local_multicast()); // IPv6 does not have local scope
1617
1618        // Test site-local multicast (scope 5)
1619        assert!(v6_site_local_mcast.is_site_local_multicast());
1620        assert!(!v6_org_local_mcast.is_site_local_multicast());
1621        assert!(!v6_admin_local_mcast.is_site_local_multicast());
1622        assert!(!v6_link_local_mcast.is_site_local_multicast());
1623        assert!(!v6_not_mcast.is_site_local_multicast());
1624        assert!(!v4_mcast.is_site_local_multicast());
1625
1626        // Test organization-local multicast
1627        // IPv6 (scope 8)
1628        assert!(!v6_site_local_mcast.is_org_local_multicast());
1629        assert!(v6_org_local_mcast.is_org_local_multicast());
1630        assert!(!v6_admin_local_mcast.is_org_local_multicast());
1631        assert!(!v6_link_local_mcast.is_org_local_multicast());
1632        assert!(!v6_not_mcast.is_org_local_multicast());
1633
1634        // IPv4 (239.192.0.0/14)
1635        let v4_org_local_mcast: IpNet = "239.192.0.1/32".parse().unwrap();
1636        let v4_org_local_mcast_end: IpNet = "239.195.255.255/32".parse().unwrap();
1637        let v4_not_org_local: IpNet = "239.196.0.0/32".parse().unwrap();
1638        assert!(v4_org_local_mcast.is_org_local_multicast());
1639        assert!(v4_org_local_mcast_end.is_org_local_multicast());
1640        assert!(!v4_not_org_local.is_org_local_multicast());
1641        assert!(!v4_mcast.is_org_local_multicast()); // 224.0.0.1 is not in 239.192/14
1642    }
1643
1644    #[test]
1645    fn test_ipv6_multicast_scope() {
1646        use MulticastScopeV6::*;
1647
1648        let link_local: Ipv6Net = "ff02::1/128".parse().unwrap();
1649        let admin_local: Ipv6Net = "ff04::1/128".parse().unwrap();
1650        let site_local: Ipv6Net = "ff05::1/128".parse().unwrap();
1651        let org_local: Ipv6Net = "ff08::1/128".parse().unwrap();
1652        let global: Ipv6Net = "ff0e::1/128".parse().unwrap();
1653        let not_mcast: Ipv6Net = "2001:db8::1/64".parse().unwrap();
1654
1655        assert_eq!(link_local.multicast_scope(), Some(LinkLocal));
1656        assert_eq!(admin_local.multicast_scope(), Some(AdminLocal));
1657        assert_eq!(site_local.multicast_scope(), Some(SiteLocal));
1658        assert_eq!(org_local.multicast_scope(), Some(OrganizationLocal));
1659        assert_eq!(global.multicast_scope(), Some(Global));
1660        assert_eq!(not_mcast.multicast_scope(), None);
1661
1662        // Test is_admin_scoped_multicast
1663        assert!(!LinkLocal.is_admin_scoped_multicast());
1664        assert!(AdminLocal.is_admin_scoped_multicast());
1665        assert!(SiteLocal.is_admin_scoped_multicast());
1666        assert!(OrganizationLocal.is_admin_scoped_multicast());
1667        assert!(!Global.is_admin_scoped_multicast());
1668    }
1669
1670    #[cfg(feature = "ula")]
1671    #[test]
1672    fn test_ipv6_ula_builder() {
1673        // ULAs built without any parameters should result in a random /48 in
1674        // fd::/8.
1675        let ula1 = UlaBuilder::default().build().unwrap();
1676        let ula2 = UlaBuilder::default().build().unwrap();
1677        assert_eq!(ula1.width(), 48);
1678        assert_ne!(ula1, ula2);
1679
1680        // If the id is not specified, it will be random, so these
1681        // should still not match.
1682        let t = SystemTime::now();
1683        let ula1 = UlaBuilder::default().date(t).build().unwrap();
1684        let ula2 = UlaBuilder::default().date(t).build().unwrap();
1685        assert_ne!(ula1, ula2);
1686
1687        // Builders where the date and ID are specified should be
1688        // deterministic.
1689        let ula1 = UlaBuilder::default()
1690            .date(t)
1691            .id(vec![1, 2, 3, 4])
1692            .build()
1693            .unwrap();
1694        let ula2 = UlaBuilder::default()
1695            .date(t)
1696            .id(vec![1, 2, 3, 4])
1697            .build()
1698            .unwrap();
1699        assert_eq!(ula1, ula2);
1700    }
1701
1702    #[test]
1703    fn test_ipv6_resize() {
1704        let s56: Ipv6Net = "fd00:a:b:cc00::/56".parse().unwrap();
1705
1706        // Extend a /56 to a /64
1707        let s64 = s56.resize(64, 0xdd).unwrap();
1708        assert_eq!(s64, "fd00:a:b:ccdd::/64".parse().unwrap());
1709
1710        // Truncate a /56 to a /48
1711        let s48 = s56.resize(48, 0).unwrap();
1712        assert_eq!(s48, "fd00:a:b::/48".parse().unwrap());
1713
1714        // Extending to a /200 should be an error
1715        assert_eq!(s56.resize(200, 0), Result::Err(IpNetPrefixError(200)));
1716
1717        // Operating on non-cannonical form
1718        let s56: Ipv6Net = "fd00:a:b:ccdd::/56".parse().unwrap();
1719        let s64 = s56.resize(64, 0).unwrap();
1720        assert_eq!(s64, "fd00:a:b:ccdd::/64".parse().unwrap());
1721        let s64 = s56.resize(64, 0xff).unwrap();
1722        assert_eq!(s64, "fd00:a:b:ccff::/64".parse().unwrap());
1723    }
1724
1725    #[test]
1726    fn test_ipv4_resize() {
1727        let s16: Ipv4Net = "10.1.0.0/16".parse().unwrap();
1728
1729        // Extend a /16 to a /24
1730        let s24 = s16.resize(24, 2).unwrap();
1731        assert_eq!(s24, "10.1.2.0/24".parse().unwrap());
1732
1733        // Truncate a /16 to a /8
1734        let s8 = s24.resize(8, 0).unwrap();
1735        assert_eq!(s8, "10.0.0.0/8".parse().unwrap());
1736
1737        // Extending to a /40 should be an error
1738        assert_eq!(s16.resize(40, 0), Result::Err(IpNetPrefixError(40)));
1739
1740        // Operating on non-cannonical form
1741        let s16: Ipv4Net = "10.1.2.3/16".parse().unwrap();
1742        // Extend a /16 to a /24
1743        let s24 = s16.resize(24, 0).unwrap();
1744        assert_eq!(s24, "10.1.2.3/24".parse().unwrap());
1745        let s24 = s16.resize(24, 255).unwrap();
1746        assert_eq!(s24, "10.1.255.3/24".parse().unwrap());
1747    }
1748}