Skip to main content

bare_types/net/
ipv6range.rs

1//! IPv6 address range types for network programming.
2//!
3//! This module provides type-safe abstractions for IPv6 address ranges,
4//! allowing representation of contiguous IPv6 address ranges.
5
6use core::fmt;
7use core::net::Ipv6Addr;
8use core::str::FromStr;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// Error type for IPv6 range parsing.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[non_exhaustive]
17pub enum Ipv6RangeError {
18    /// Invalid range format
19    ///
20    /// The input string is not in the correct range format.
21    /// Expected format: "start-end" (e.g., "2001:db8::1-2001:db8::ffff").
22    InvalidFormat,
23    /// Invalid IP address
24    ///
25    /// One of the IP addresses in the range is invalid.
26    InvalidIpAddr,
27    /// Invalid range
28    ///
29    /// The start address is greater than the end address.
30    InvalidRange,
31}
32
33impl fmt::Display for Ipv6RangeError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::InvalidFormat => write!(f, "invalid IPv6 range format"),
37            Self::InvalidIpAddr => write!(f, "invalid IP address"),
38            Self::InvalidRange => write!(f, "invalid range: start > end"),
39        }
40    }
41}
42
43#[cfg(feature = "std")]
44impl std::error::Error for Ipv6RangeError {}
45
46/// An IPv6 address range (start to end inclusive).
47///
48/// # Examples
49///
50/// ```rust
51/// use bare_types::net::Ipv6Range;
52/// use core::net::Ipv6Addr;
53///
54/// // Parse range notation
55/// let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
56///
57/// // Check if IP is in range
58/// assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x100)));
59/// ```
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
62pub struct Ipv6Range {
63    /// Start IP address (inclusive)
64    start: Ipv6Addr,
65    /// End IP address (inclusive)
66    end: Ipv6Addr,
67}
68
69impl Ipv6Range {
70    /// Creates a new IPv6 range from start and end addresses.
71    ///
72    /// # Errors
73    ///
74    /// Returns `Ipv6RangeError::InvalidRange` if start > end.
75    ///
76    /// # Examples
77    ///
78    /// ```rust
79    /// use bare_types::net::Ipv6Range;
80    /// use core::net::Ipv6Addr;
81    ///
82    /// let range = Ipv6Range::new(
83    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1),
84    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
85    /// ).unwrap();
86    /// ```
87    #[inline]
88    pub fn new(start: Ipv6Addr, end: Ipv6Addr) -> Result<Self, Ipv6RangeError> {
89        let start_u128 = u128::from(start);
90        let end_u128 = u128::from(end);
91
92        if start_u128 > end_u128 {
93            return Err(Ipv6RangeError::InvalidRange);
94        }
95
96        Ok(Self { start, end })
97    }
98
99    /// Returns the start address.
100    ///
101    /// # Examples
102    ///
103    /// ```rust
104    /// use bare_types::net::Ipv6Range;
105    /// use core::net::Ipv6Addr;
106    ///
107    /// let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
108    /// assert_eq!(range.start(), Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1));
109    /// ```
110    #[inline]
111    #[must_use]
112    pub const fn start(&self) -> Ipv6Addr {
113        self.start
114    }
115
116    /// Returns the end address.
117    ///
118    /// # Examples
119    ///
120    /// ```rust
121    /// use bare_types::net::Ipv6Range;
122    /// use core::net::Ipv6Addr;
123    ///
124    /// let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
125    /// assert_eq!(range.end(), Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff));
126    /// ```
127    #[inline]
128    #[must_use]
129    pub const fn end(&self) -> Ipv6Addr {
130        self.end
131    }
132
133    /// Returns `true` if this range contains the given IP address.
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// use bare_types::net::Ipv6Range;
139    /// use core::net::Ipv6Addr;
140    ///
141    /// let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
142    /// assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x100)));
143    /// assert!(!range.contains(&Ipv6Addr::new(0x2001, 0x0db9, 0, 0, 0, 0, 0, 1)));
144    /// ```
145    #[inline]
146    #[must_use]
147    pub fn contains(&self, ip: &Ipv6Addr) -> bool {
148        let ip_u128 = u128::from(*ip);
149        let start_u128 = u128::from(self.start);
150        let end_u128 = u128::from(self.end);
151        ip_u128 >= start_u128 && ip_u128 <= end_u128
152    }
153
154    /// Returns the number of addresses in this range.
155    ///
156    /// # Examples
157    ///
158    /// ```rust
159    /// use bare_types::net::Ipv6Range;
160    ///
161    /// let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
162    /// assert_eq!(range.num_addresses(), 0xffff);
163    /// ```
164    #[inline]
165    #[must_use]
166    pub fn num_addresses(&self) -> u128 {
167        let start_u128 = u128::from(self.start);
168        let end_u128 = u128::from(self.end);
169        end_u128 - start_u128 + 1
170    }
171
172    /// Returns `true` if this range overlaps with another range.
173    ///
174    /// # Examples
175    ///
176    /// ```rust
177    /// use bare_types::net::Ipv6Range;
178    ///
179    /// let range1: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
180    /// let range2: Ipv6Range = "2001:db8::100-2001:db9::1".parse().unwrap();
181    /// assert!(range1.overlaps(&range2));
182    /// ```
183    #[inline]
184    #[must_use]
185    pub fn overlaps(&self, other: &Self) -> bool {
186        let self_start = u128::from(self.start);
187        let self_end = u128::from(self.end);
188        let other_start = u128::from(other.start);
189        let other_end = u128::from(other.end);
190
191        self_start <= other_end && self_end >= other_start
192    }
193
194    /// Returns `true` if this range fully contains another range.
195    ///
196    /// # Examples
197    ///
198    /// ```rust
199    /// use bare_types::net::Ipv6Range;
200    ///
201    /// let range1: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
202    /// let range2: Ipv6Range = "2001:db8::100-2001:db8::200".parse().unwrap();
203    /// assert!(range1.contains_range(&range2));
204    /// ```
205    #[inline]
206    #[must_use]
207    pub fn contains_range(&self, other: &Self) -> bool {
208        let self_start = u128::from(self.start);
209        let self_end = u128::from(self.end);
210        let other_start = u128::from(other.start);
211        let other_end = u128::from(other.end);
212
213        self_start <= other_start && self_end >= other_end
214    }
215
216    /// Returns `true` if this range is adjacent to another range.
217    ///
218    /// # Examples
219    ///
220    /// ```rust
221    /// use bare_types::net::Ipv6Range;
222    /// use core::net::Ipv6Addr;
223    ///
224    /// let range1 = Ipv6Range::new(
225    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
226    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 10),
227    /// ).unwrap();
228    /// let range2 = Ipv6Range::new(
229    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 11),
230    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20),
231    /// ).unwrap();
232    /// assert!(range1.is_adjacent_to(&range2));
233    /// ```
234    #[inline]
235    #[must_use]
236    pub fn is_adjacent_to(&self, other: &Self) -> bool {
237        let self_end = u128::from(self.end);
238        let other_start = u128::from(other.start);
239        let other_end = u128::from(other.end);
240        let self_start = u128::from(self.start);
241
242        self_end + 1 == other_start || other_end + 1 == self_start
243    }
244
245    /// Returns a merged range if this range overlaps or is adjacent to another range.
246    ///
247    /// # Examples
248    ///
249    /// ```rust
250    /// use bare_types::net::Ipv6Range;
251    /// use core::net::Ipv6Addr;
252    ///
253    /// let range1 = Ipv6Range::new(
254    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
255    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 10),
256    /// ).unwrap();
257    /// let range2 = Ipv6Range::new(
258    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 11),
259    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20),
260    /// ).unwrap();
261    /// let merged = range1.merge(&range2).unwrap();
262    /// assert_eq!(merged.start(), Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0));
263    /// assert_eq!(merged.end(), Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20));
264    /// ```
265    #[inline]
266    pub fn merge(&self, other: &Self) -> Option<Self> {
267        if self.overlaps(other) || self.is_adjacent_to(other) {
268            let self_start = u128::from(self.start);
269            let self_end = u128::from(self.end);
270            let other_start = u128::from(other.start);
271            let other_end = u128::from(other.end);
272
273            let new_start = self_start.min(other_start);
274            let new_end = self_end.max(other_end);
275
276            Some(Self {
277                start: Ipv6Addr::from(new_start),
278                end: Ipv6Addr::from(new_end),
279            })
280        } else {
281            None
282        }
283    }
284
285    /// Returns `true` if this range can be represented as a CIDR block.
286    ///
287    /// # Examples
288    ///
289    /// ```rust
290    /// use bare_types::net::Ipv6Range;
291    /// use core::net::Ipv6Addr;
292    ///
293    /// // /112 prefix: 65536 addresses (2^16)
294    /// let range = Ipv6Range::new(
295    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
296    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
297    /// ).unwrap();
298    /// assert!(range.is_cidr_compatible());
299    /// ```
300    #[inline]
301    #[must_use]
302    pub fn is_cidr_compatible(&self) -> bool {
303        let count = self.num_addresses();
304        if count == 0 {
305            return false;
306        }
307
308        // Must be power of 2
309        if count & (count - 1) != 0 {
310            return false;
311        }
312
313        // Start address must be aligned
314        let start_u128 = u128::from(self.start);
315        let align_mask = count - 1;
316        (start_u128 & align_mask) == 0
317    }
318
319    /// Returns the prefix length if this range can be represented as a CIDR block.
320    ///
321    /// # Examples
322    ///
323    /// ```rust
324    /// use bare_types::net::Ipv6Range;
325    /// use core::net::Ipv6Addr;
326    ///
327    /// // /112 prefix: 65536 addresses (2^16)
328    /// let range = Ipv6Range::new(
329    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
330    ///     Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
331    /// ).unwrap();
332    /// assert_eq!(range.cidr_prefix_len(), Some(112));
333    /// ```
334    #[inline]
335    #[must_use]
336    pub fn cidr_prefix_len(&self) -> Option<u8> {
337        if self.is_cidr_compatible() {
338            let count = self.num_addresses();
339            // For IPv6: prefix_len = 128 - log2(count) = 128 - (127 - count.leading_zeros()) = count.leading_zeros() + 1
340            let prefix_len = (count.leading_zeros() + 1) as u8;
341            Some(prefix_len)
342        } else {
343            None
344        }
345    }
346}
347
348impl FromStr for Ipv6Range {
349    type Err = Ipv6RangeError;
350
351    fn from_str(s: &str) -> Result<Self, Self::Err> {
352        let Some((start_str, end_str)) = s.split_once('-') else {
353            return Err(Ipv6RangeError::InvalidFormat);
354        };
355
356        let start: Ipv6Addr = start_str
357            .parse()
358            .map_err(|_| Ipv6RangeError::InvalidIpAddr)?;
359
360        let end: Ipv6Addr = end_str.parse().map_err(|_| Ipv6RangeError::InvalidIpAddr)?;
361
362        Self::new(start, end)
363    }
364}
365
366impl fmt::Display for Ipv6Range {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        write!(f, "{}-{}", self.start, self.end)
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn test_new_valid() {
378        let range = Ipv6Range::new(
379            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1),
380            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
381        )
382        .unwrap();
383        assert_eq!(
384            range.start(),
385            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)
386        );
387        assert_eq!(
388            range.end(),
389            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff)
390        );
391    }
392
393    #[test]
394    fn test_new_invalid_range() {
395        assert!(
396            Ipv6Range::new(
397                Ipv6Addr::new(0x2001, 0x0db9, 0, 0, 0, 0, 0, 1),
398                Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
399            )
400            .is_err()
401        );
402    }
403
404    #[test]
405    fn test_parse() {
406        let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
407        assert_eq!(
408            range.start(),
409            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)
410        );
411        assert_eq!(
412            range.end(),
413            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff)
414        );
415    }
416
417    #[test]
418    fn test_parse_invalid_format() {
419        assert!("2001:db8::1".parse::<Ipv6Range>().is_err());
420        assert!("2001:db8::1/64".parse::<Ipv6Range>().is_err());
421    }
422
423    #[test]
424    fn test_contains() {
425        let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
426        assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)));
427        assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff)));
428        assert!(range.contains(&Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x100)));
429        assert!(!range.contains(&Ipv6Addr::new(0x2001, 0x0db9, 0, 0, 0, 0, 0, 1)));
430    }
431
432    #[test]
433    fn test_num_addresses() {
434        let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
435        assert_eq!(range.num_addresses(), 0xffff);
436
437        let single: Ipv6Range = "2001:db8::1-2001:db8::1".parse().unwrap();
438        assert_eq!(single.num_addresses(), 1);
439    }
440
441    #[test]
442    fn test_overlaps() {
443        let range1: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
444        let range2: Ipv6Range = "2001:db8::100-2001:db9::1".parse().unwrap();
445        let range3: Ipv6Range = "2001:db9::1-2001:db9::ffff".parse().unwrap();
446
447        assert!(range1.overlaps(&range2));
448        assert!(range2.overlaps(&range1));
449        assert!(!range1.overlaps(&range3));
450    }
451
452    #[test]
453    fn test_display() {
454        let range: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
455        assert_eq!(format!("{}", range), "2001:db8::1-2001:db8::ffff");
456    }
457
458    #[test]
459    fn test_contains_range() {
460        let range1: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
461        let range2: Ipv6Range = "2001:db8::100-2001:db8::200".parse().unwrap();
462        let range3: Ipv6Range = "2001:db8::1-2001:db9::ffff".parse().unwrap();
463
464        assert!(range1.contains_range(&range2));
465        assert!(!range2.contains_range(&range1));
466        assert!(!range1.contains_range(&range3));
467    }
468
469    #[test]
470    fn test_is_adjacent_to() {
471        let range1 = Ipv6Range::new(
472            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
473            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 10),
474        )
475        .unwrap();
476        let range2 = Ipv6Range::new(
477            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 11),
478            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20),
479        )
480        .unwrap();
481        let range3 = Ipv6Range::new(
482            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 30),
483            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 40),
484        )
485        .unwrap();
486
487        assert!(range1.is_adjacent_to(&range2));
488        assert!(range2.is_adjacent_to(&range1));
489        assert!(!range1.is_adjacent_to(&range3));
490    }
491
492    #[test]
493    fn test_merge() {
494        let range1 = Ipv6Range::new(
495            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
496            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 10),
497        )
498        .unwrap();
499        let range2 = Ipv6Range::new(
500            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 11),
501            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20),
502        )
503        .unwrap();
504        let merged = range1.merge(&range2).unwrap();
505        assert_eq!(
506            merged.start(),
507            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0)
508        );
509        assert_eq!(
510            merged.end(),
511            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 20)
512        );
513    }
514
515    #[test]
516    fn test_is_cidr_compatible() {
517        let cidr = Ipv6Range::new(
518            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
519            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
520        )
521        .unwrap();
522        assert!(cidr.is_cidr_compatible());
523
524        let non_cidr: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
525        assert!(!non_cidr.is_cidr_compatible());
526    }
527
528    #[test]
529    fn test_cidr_prefix_len() {
530        let cidr = Ipv6Range::new(
531            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0),
532            Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0xffff),
533        )
534        .unwrap();
535        assert_eq!(cidr.cidr_prefix_len(), Some(112));
536
537        let single: Ipv6Range = "2001:db8::1-2001:db8::1".parse().unwrap();
538        assert_eq!(single.cidr_prefix_len(), Some(128));
539
540        let non_cidr: Ipv6Range = "2001:db8::1-2001:db8::ffff".parse().unwrap();
541        assert_eq!(non_cidr.cidr_prefix_len(), None);
542    }
543}