1use core::fmt;
7use core::net::Ipv6Addr;
8use core::str::FromStr;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[non_exhaustive]
17pub enum Ipv6RangeError {
18 InvalidFormat,
23 InvalidIpAddr,
27 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
62pub struct Ipv6Range {
63 start: Ipv6Addr,
65 end: Ipv6Addr,
67}
68
69impl Ipv6Range {
70 #[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 #[inline]
111 #[must_use]
112 pub const fn start(&self) -> Ipv6Addr {
113 self.start
114 }
115
116 #[inline]
128 #[must_use]
129 pub const fn end(&self) -> Ipv6Addr {
130 self.end
131 }
132
133 #[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 #[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 #[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 #[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 #[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 #[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 #[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 if count & (count - 1) != 0 {
310 return false;
311 }
312
313 let start_u128 = u128::from(self.start);
315 let align_mask = count - 1;
316 (start_u128 & align_mask) == 0
317 }
318
319 #[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 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}