network_device/
interface.rs1use std::fmt;
4use std::net::IpAddr;
5use std::str::FromStr;
6
7#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub struct InterfaceName(String);
13
14impl InterfaceName {
15 pub fn new(value: impl Into<String>) -> Result<Self, InterfaceNameError> {
17 let value = value.into();
18 if value.trim().is_empty() {
19 return Err(InterfaceNameError::Empty);
20 }
21 if value.chars().any(char::is_control) {
22 return Err(InterfaceNameError::ControlCharacter);
23 }
24 Ok(Self(value))
25 }
26
27 pub fn as_str(&self) -> &str {
29 &self.0
30 }
31}
32
33impl FromStr for InterfaceName {
34 type Err = InterfaceNameError;
35
36 fn from_str(value: &str) -> Result<Self, Self::Err> {
37 Self::new(value)
38 }
39}
40
41impl TryFrom<&str> for InterfaceName {
42 type Error = InterfaceNameError;
43
44 fn try_from(value: &str) -> Result<Self, Self::Error> {
45 Self::new(value)
46 }
47}
48
49impl fmt::Display for InterfaceName {
50 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51 formatter.write_str(self.as_str())
52 }
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum InterfaceNameError {
58 Empty,
60 ControlCharacter,
62}
63
64impl fmt::Display for InterfaceNameError {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 formatter.write_str(match self {
67 Self::Empty => "interface name must not be empty or whitespace-only",
68 Self::ControlCharacter => "interface name must not contain control characters",
69 })
70 }
71}
72
73impl std::error::Error for InterfaceNameError {}
74
75#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
77pub struct IpPrefix {
78 address: IpAddr,
80 prefix_length: u8,
82}
83
84impl IpPrefix {
85 pub fn new(address: IpAddr, prefix_length: u8) -> Result<Self, IpPrefixError> {
87 let maximum = match address {
88 IpAddr::V4(_) => 32,
89 IpAddr::V6(_) => 128,
90 };
91 if prefix_length > maximum {
92 return Err(IpPrefixError {
93 prefix_length,
94 maximum,
95 });
96 }
97 Ok(Self {
98 address,
99 prefix_length,
100 })
101 }
102
103 pub const fn address(self) -> IpAddr {
105 self.address
106 }
107
108 pub const fn prefix_length(self) -> u8 {
110 self.prefix_length
111 }
112}
113
114impl FromStr for IpPrefix {
115 type Err = IpPrefixParseError;
116
117 fn from_str(value: &str) -> Result<Self, Self::Err> {
118 let (address, length) = value
119 .rsplit_once('/')
120 .ok_or(IpPrefixParseError::MissingPrefixLength)?;
121 let address = address
122 .parse::<IpAddr>()
123 .map_err(|_| IpPrefixParseError::InvalidAddress)?;
124 let length = length
125 .parse::<u8>()
126 .map_err(|_| IpPrefixParseError::InvalidPrefixLength)?;
127 Self::new(address, length).map_err(IpPrefixParseError::OutOfRange)
128 }
129}
130
131impl fmt::Display for IpPrefix {
132 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(formatter, "{}/{}", self.address, self.prefix_length)
134 }
135}
136
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
139pub struct IpPrefixError {
140 pub prefix_length: u8,
142 pub maximum: u8,
144}
145
146impl fmt::Display for IpPrefixError {
147 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148 write!(
149 formatter,
150 "prefix length {} exceeds maximum {}",
151 self.prefix_length, self.maximum
152 )
153 }
154}
155
156impl std::error::Error for IpPrefixError {}
157
158#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum IpPrefixParseError {
161 MissingPrefixLength,
163 InvalidAddress,
165 InvalidPrefixLength,
167 OutOfRange(IpPrefixError),
169}
170
171impl fmt::Display for IpPrefixParseError {
172 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::MissingPrefixLength => formatter.write_str("IP prefix must include /length"),
175 Self::InvalidAddress => formatter.write_str("invalid IP address in prefix"),
176 Self::InvalidPrefixLength => formatter.write_str("invalid IP prefix length"),
177 Self::OutOfRange(error) => error.fmt(formatter),
178 }
179 }
180}
181
182impl std::error::Error for IpPrefixParseError {}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn interface_names_reject_empty_and_control_characters() {
190 assert_eq!(InterfaceName::new(" "), Err(InterfaceNameError::Empty));
191 assert_eq!(
192 InterfaceName::new("eth\n0"),
193 Err(InterfaceNameError::ControlCharacter)
194 );
195 assert_eq!(
196 InterfaceName::new("Ethernet 1/1").unwrap().as_str(),
197 "Ethernet 1/1"
198 );
199 }
200
201 #[test]
202 fn prefixes_validate_both_address_families() {
203 assert_eq!(
204 "192.0.2.1/32".parse::<IpPrefix>().unwrap().prefix_length(),
205 32
206 );
207 assert_eq!(
208 "2001:db8::1/128"
209 .parse::<IpPrefix>()
210 .unwrap()
211 .prefix_length(),
212 128
213 );
214 assert_eq!(
215 "192.0.2.1/33".parse::<IpPrefix>(),
216 Err(IpPrefixParseError::OutOfRange(IpPrefixError {
217 prefix_length: 33,
218 maximum: 32,
219 }))
220 );
221 }
222}