Skip to main content

oxnet/
ipnet.rs

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