1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::net::IpAddr;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct CidrBlock {
9 pub address: String,
11 pub prefix: u8,
13}
14
15pub fn parse_cidr(input: &str) -> Option<CidrBlock> {
17 let trimmed = input.trim();
18
19 if trimmed.is_empty() {
20 return None;
21 }
22
23 let (address, prefix) = trimmed.split_once('/')?;
24 let address = address.parse::<IpAddr>().ok()?;
25 let prefix = prefix.parse::<u8>().ok()?;
26
27 let valid_prefix = match address {
28 IpAddr::V4(_) => is_valid_ipv4_prefix(prefix),
29 IpAddr::V6(_) => is_valid_ipv6_prefix(prefix),
30 };
31
32 if !valid_prefix {
33 return None;
34 }
35
36 Some(CidrBlock {
37 address: address.to_string(),
38 prefix,
39 })
40}
41
42pub fn format_cidr(block: &CidrBlock) -> String {
44 format!("{}/{}", block.address, block.prefix)
45}
46
47pub fn is_cidr(input: &str) -> bool {
49 parse_cidr(input).is_some()
50}
51
52pub fn is_ipv4_cidr(input: &str) -> bool {
54 parse_cidr(input).is_some_and(|block| {
55 block
56 .address
57 .parse::<IpAddr>()
58 .is_ok_and(|address| matches!(address, IpAddr::V4(_)))
59 })
60}
61
62pub fn is_ipv6_cidr(input: &str) -> bool {
64 parse_cidr(input).is_some_and(|block| {
65 block
66 .address
67 .parse::<IpAddr>()
68 .is_ok_and(|address| matches!(address, IpAddr::V6(_)))
69 })
70}
71
72pub fn is_valid_ipv4_prefix(prefix: u8) -> bool {
74 prefix <= 32
75}
76
77pub fn is_valid_ipv6_prefix(prefix: u8) -> bool {
79 prefix <= 128
80}