Skip to main content

bare_types/net/
ipv4range.rs

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