irox_networking/address.rs
1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5use core::fmt::{Display, Formatter};
6use core::str::FromStr;
7
8use irox_tools::arrays::longest_consecutive_values;
9use irox_tools::options::MaybeMap;
10use irox_tools::u16::{FromU16Array, ToU16Array};
11
12///
13/// A Layer-2 Ethernet Media-Access-Control Address (MAC)
14#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
15pub struct MAC {
16 bytes: [u8; 6],
17}
18
19///
20/// A generic Internet Protocol network. Could be either an [`IPv4Network`] or an [`IPv6Network`]
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
22pub enum IPNetwork {
23 IPv4(IPv4Network),
24 IPv6(IPv6Network),
25}
26
27///
28/// An error returned by the various processing functions.
29#[derive(Debug, Copy, Clone, Eq, PartialEq)]
30pub enum NetworkError {
31 /// The specified CIDR is not a valid CIDR
32 InvalidCIDR(u8),
33 /// The specified mask is not a valid mask of the type required.
34 InvalidMask(u32),
35 /// The specified number is not a power-of-two
36 NotAPowerOfTwo(u32),
37 /// The specified [`IPAddress`] does not represent a network ID, but is a host or a broadcast.
38 NotANetworkAddress(IPAddress),
39}
40
41#[derive(Debug, Clone, Eq, PartialEq)]
42pub enum AddressError {
43 InvalidAddress,
44}
45impl Display for AddressError {
46 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47 write!(f, "{self:?}")
48 }
49}
50
51/// A generic Internet Protocol Address, could be a [`IPv4Address`] or a [`IPv6Address`]
52#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
53pub enum IPAddress {
54 IPv4(IPv4Address),
55 IPv6(IPv6Address),
56}
57impl IPAddress {
58 pub fn sockaddr(&self, port: u16) -> std::net::SocketAddr {
59 std::net::SocketAddr::new(self.into(), port)
60 }
61}
62
63impl From<IPAddress> for std::net::IpAddr {
64 fn from(value: IPAddress) -> Self {
65 (&value).into()
66 }
67}
68impl From<&IPAddress> for std::net::IpAddr {
69 fn from(value: &IPAddress) -> Self {
70 match value {
71 IPAddress::IPv4(i) => std::net::IpAddr::V4(i.into()),
72 IPAddress::IPv6(i) => std::net::IpAddr::V6(i.into()),
73 }
74 }
75}
76
77/// A 32-bit Internet Protocol Version 4 address as specified in RFC791
78#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
79pub struct IPv4Address {
80 pub(crate) address: u32,
81}
82
83impl IPv4Address {
84 ///
85 /// Creates a new IPv4Address from Big-Endian Bytes.
86 ///
87 /// # Example:
88 /// ```
89 /// # use irox_networking::address::IPv4Address;
90 /// let addr = IPv4Address::from_be_bytes(&[127,0,0,1]);
91 ///
92 /// assert_eq!("127.0.0.1", format!("{}", addr));
93 /// ```
94 pub fn from_be_bytes(bytes: &[u8; 4]) -> IPv4Address {
95 bytes.into()
96 }
97
98 pub fn sockaddr(&self, port: u16) -> std::net::SocketAddr {
99 std::net::SocketAddr::new(self.into(), port)
100 }
101}
102
103impl Display for IPv4Address {
104 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105 let [a, b, c, d] = self.address.to_be_bytes();
106 f.write_fmt(format_args!("{a}.{b}.{c}.{d}"))
107 }
108}
109
110impl FromStr for IPv4Address {
111 type Err = AddressError;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 Ok(if s.contains('.') {
115 // assume standard dotted-decimal
116 let mut split = s.split('.');
117 let Some(first) = split.next().maybe_map(|v| u8::from_str(v).ok()) else {
118 return Err(AddressError::InvalidAddress);
119 };
120 let Some(second) = split.next().maybe_map(|v| u8::from_str(v).ok()) else {
121 return Err(AddressError::InvalidAddress);
122 };
123 let Some(third) = split.next().maybe_map(|v| u8::from_str(v).ok()) else {
124 return Err(AddressError::InvalidAddress);
125 };
126 let Some(fourth) = split.next().maybe_map(|v| u8::from_str(v).ok()) else {
127 return Err(AddressError::InvalidAddress);
128 };
129
130 IPv4Address::from_be_bytes(&[first, second, third, fourth])
131 } else if s.starts_with("0x") {
132 // assume hex 32-bit
133 let Ok(val) = u32::from_str_radix(s, 16) else {
134 return Err(AddressError::InvalidAddress);
135 };
136 IPv4Address::from(val)
137 } else {
138 // assume 32-bit int.
139 let Ok(val) = u32::from_str(s) else {
140 return Err(AddressError::InvalidAddress);
141 };
142 IPv4Address::from(val)
143 })
144 }
145}
146
147impl From<u32> for IPv4Address {
148 fn from(value: u32) -> Self {
149 IPv4Address { address: value }
150 }
151}
152
153impl From<[u8; 4]> for IPv4Address {
154 fn from(value: [u8; 4]) -> Self {
155 let address = u32::from_be_bytes(value);
156 IPv4Address { address }
157 }
158}
159
160impl From<&[u8; 4]> for IPv4Address {
161 fn from(value: &[u8; 4]) -> Self {
162 let address = u32::from_be_bytes(*value);
163 IPv4Address { address }
164 }
165}
166
167impl From<IPv4Address> for std::net::Ipv4Addr {
168 fn from(value: IPv4Address) -> Self {
169 std::net::Ipv4Addr::from(value.address)
170 }
171}
172impl From<&IPv4Address> for std::net::Ipv4Addr {
173 fn from(value: &IPv4Address) -> Self {
174 std::net::Ipv4Addr::from(value.address)
175 }
176}
177impl From<&IPv4Address> for std::net::IpAddr {
178 fn from(value: &IPv4Address) -> Self {
179 std::net::IpAddr::V4(value.into())
180 }
181}
182impl From<IPv4Address> for std::net::IpAddr {
183 fn from(value: IPv4Address) -> Self {
184 std::net::IpAddr::V4(value.into())
185 }
186}
187///
188/// An Internet Protocol Version 4 Network, an [`IPv4Address`] and a Netmask/CIDR.
189#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
190pub struct IPv4Network {
191 pub(crate) network_id: IPv4Address,
192 pub(crate) network_mask: u32,
193 pub(crate) host_mask: u32,
194 pub(crate) cidr: u32,
195}
196
197impl IPv4Network {
198 ///
199 /// Creates a new [`IPv4Network`] with the specified CIDR number.
200 ///
201 /// # Example
202 /// ```
203 ///
204 /// # use irox_networking::address::IPv4Network;
205 /// # use irox_networking::address::{IPv4Address, NetworkError};
206 /// let addr = IPv4Address::from_be_bytes(&[127,0,0,0]);
207 /// let network = IPv4Network::from_cidr(addr, 16).unwrap();
208 ///
209 /// assert_eq!("127.0.0.0/16", format!("{network}"));
210 ///
211 /// ```
212 ///
213 pub fn from_cidr(network_id: IPv4Address, cidr: u8) -> Result<IPv4Network, NetworkError> {
214 if cidr > 32 {
215 return Err(NetworkError::InvalidCIDR(cidr));
216 }
217 let host_mask: u32 = (1 << (32 - cidr)) - 1;
218 let network_mask = !host_mask;
219 if host_mask & network_id.address > 0 {
220 return Err(NetworkError::NotANetworkAddress(IPAddress::IPv4(
221 network_id,
222 )));
223 }
224 let cidr = network_mask.leading_ones();
225 Ok(IPv4Network {
226 network_id,
227 network_mask,
228 host_mask,
229 cidr,
230 })
231 }
232
233 ///
234 /// Creates a new [`IPv4Network`] from a specified power-of-two count of network addresses. This
235 /// is semantically equivalent to a CIDR, using `2^(32-cidr)`. For a `/24` network, use `256`.
236 ///
237 /// # Example
238 /// ```
239 /// # use irox_networking::address::IPv4Network;
240 /// # use irox_networking::address::{IPv4Address, NetworkError};
241 /// let addr = IPv4Address::from_be_bytes(&[127,0,0,0]);
242 /// let network = IPv4Network::from_address_count(addr, 256).unwrap();
243 ///
244 /// assert_eq!("127.0.0.0/24", format!("{network}"));
245 /// ```
246 pub fn from_address_count(
247 network_id: IPv4Address,
248 address_count: u32,
249 ) -> Result<IPv4Network, NetworkError> {
250 if !address_count.is_power_of_two() {
251 return Err(NetworkError::NotAPowerOfTwo(address_count));
252 }
253 let host_mask: u32 = address_count - 1;
254 let network_mask = !host_mask;
255 if host_mask & network_id.address > 0 {
256 return Err(NetworkError::NotANetworkAddress(IPAddress::IPv4(
257 network_id,
258 )));
259 }
260 let cidr = network_mask.leading_ones();
261 Ok(IPv4Network {
262 network_id,
263 host_mask,
264 network_mask,
265 cidr,
266 })
267 }
268
269 ///
270 /// Creates a new [`IPv4Network`] using the specified network ID and network Mask.
271 ///
272 /// # Example
273 /// ```
274 ///
275 /// # use irox_networking::address::IPv4Network;
276 /// # use irox_networking::address::{IPv4Address, NetworkError};
277 /// let addr = IPv4Address::from_be_bytes(&[127,0,0,0]);
278 /// let network = IPv4Network::from_network_mask(addr, 0xFFFFFF00).unwrap();
279 ///
280 /// assert_eq!("127.0.0.0/24", format!("{network}"));
281 ///
282 /// ```
283 pub fn from_network_mask(
284 network_id: IPv4Address,
285 network_mask: u32,
286 ) -> Result<IPv4Network, NetworkError> {
287 if network_mask.leading_ones() + network_mask.trailing_zeros() != 32 {
288 return Err(NetworkError::InvalidMask(network_mask));
289 }
290 let host_mask = !network_mask;
291 if host_mask & network_id.address > 0 {
292 return Err(NetworkError::NotANetworkAddress(IPAddress::IPv4(
293 network_id,
294 )));
295 }
296 let cidr = network_mask.leading_ones();
297 Ok(IPv4Network {
298 network_id,
299 network_mask,
300 host_mask,
301 cidr,
302 })
303 }
304
305 ///
306 /// Creates an [`IPv4Network`] from a network ID and host mask. A host mask is the inverted form
307 /// of a network mask. If a `/24` is represented by `0xFFFFFF00`, then the equivalent host mask
308 /// is `0x000000FF`
309 ///
310 /// # Example
311 /// ```
312 ///
313 /// # use irox_networking::address::IPv4Network;
314 /// # use irox_networking::address::{IPv4Address, NetworkError};
315 /// let addr = IPv4Address::from_be_bytes(&[127,0,0,0]);
316 /// let network = IPv4Network::from_host_mask(addr, 0x000000FF).unwrap();
317 ///
318 /// assert_eq!("127.0.0.0/24", format!("{network}"));
319 ///
320 /// ```
321 pub fn from_host_mask(
322 network_id: IPv4Address,
323 host_mask: u32,
324 ) -> Result<IPv4Network, NetworkError> {
325 if host_mask.leading_zeros() + host_mask.trailing_ones() != 32 {
326 return Err(NetworkError::InvalidMask(host_mask));
327 }
328 let network_mask = !host_mask;
329 if host_mask & network_id.address > 0 {
330 return Err(NetworkError::NotANetworkAddress(IPAddress::IPv4(
331 network_id,
332 )));
333 }
334 let cidr = network_mask.leading_ones();
335 Ok(IPv4Network {
336 network_id,
337 network_mask,
338 host_mask,
339 cidr,
340 })
341 }
342
343 ///
344 /// Creates a [`IPv4Network`] using the specified IPv4 Network ID and the specified CIDR.
345 ///
346 /// # Example
347 /// ```
348 /// # use irox_networking::address::IPv4Network;
349 /// let network = IPv4Network::from_net_and_cidr(&[127,0,0,0], 24).unwrap();
350 ///
351 /// assert_eq!("127.0.0.0/24", format!("{network}"));
352 /// ```
353 pub fn from_net_and_cidr(network_id: &[u8; 4], cidr: u8) -> Result<IPv4Network, NetworkError> {
354 let network_id: IPv4Address = network_id.into();
355 Self::from_cidr(network_id, cidr)
356 }
357
358 ///
359 /// Returns true if the specified address is the network address for this network.
360 ///
361 /// # Example
362 /// ```
363 /// # use irox_networking::address::{IPv4Address, IPv4Network};
364 /// let net_addr = IPv4Address::from(&[127,0,0,0]);
365 /// let host_addr = IPv4Address::from(&[127,0,0,1]);
366 /// let network = IPv4Network::from_cidr(net_addr, 24).unwrap();
367 ///
368 /// assert_eq!(true, network.is_network_address(net_addr));
369 /// assert_eq!(false, network.is_network_address(host_addr));
370 /// ```
371 pub fn is_network_address(&self, address: IPv4Address) -> bool {
372 address == self.network_id
373 }
374
375 ///
376 /// Returns true if the specified address is the broadcast address for this network.
377 ///
378 /// # Example
379 /// ```
380 /// # use irox_networking::address::{IPv4Address, IPv4Network};
381 /// let net_addr = IPv4Address::from(&[127,0,0,0]);
382 /// let broadcast = IPv4Address::from(&[127,0,0,255]);
383 /// let network = IPv4Network::from_cidr(net_addr, 24).unwrap();
384 ///
385 /// assert_eq!(true, network.is_broadcast_address(broadcast));
386 /// assert_eq!(false, network.is_broadcast_address(net_addr));
387 /// ```
388 pub fn is_broadcast_address(&self, address: IPv4Address) -> bool {
389 address.address & self.host_mask == self.host_mask
390 }
391
392 ///
393 /// Returns true if this address represents a private address range, in
394 /// `10.0.0.0/8` or `172.16.0.0/12` or `192.168.0.0/16`
395 ///
396 /// # Example
397 /// ```
398 /// # use irox_networking::address::IPv4Network;
399 /// let home_network = IPv4Network::from_net_and_cidr(&[192,168,0,0], 24).unwrap();
400 /// assert_eq!(true, home_network.is_private_address());
401 ///
402 /// let enterprise_network = IPv4Network::from_net_and_cidr(&[10,10,0,0], 16).unwrap();
403 /// assert_eq!(true, enterprise_network.is_private_address());
404 ///
405 /// let hotel_network = IPv4Network::from_net_and_cidr(&[172,20,0,0], 14).unwrap();
406 /// assert_eq!(true, hotel_network.is_private_address());
407 ///
408 /// let quad_eight = IPv4Network::from_net_and_cidr(&[8,8,8,8], 32).unwrap();
409 /// assert_eq!(false, quad_eight.is_private_address());
410 /// ```
411 pub fn is_private_address(&self) -> bool {
412 let net = self.network_id.address;
413 net & 0xFF000000 == 0x0A000000
414 || net & 0xFFF00000 == 0xAC100000
415 || net & 0xFFFF0000 == 0xC0A80000
416 }
417
418 ///
419 /// Returns true if this network address represents a link-local address, in `169.254.0.0/16`
420 ///
421 /// # Example
422 /// ```
423 /// # use irox_networking::address::IPv4Network;
424 /// let link_local = IPv4Network::from_net_and_cidr(&[169,254,55,228], 32).unwrap();
425 /// assert_eq!(true, link_local.is_link_local());
426 ///
427 /// let quad_eight = IPv4Network::from_net_and_cidr(&[8,8,8,8], 32).unwrap();
428 /// assert_eq!(false, quad_eight.is_link_local());
429 /// ```
430 pub fn is_link_local(&self) -> bool {
431 let net = self.network_id.address;
432 net & 0xFFFF0000 == 0xA9FE0000
433 }
434
435 ///
436 /// Returns true if this network address represents a loopback address, in `127.0.0.0/8`
437 ///
438 /// # Example
439 /// ```
440 /// # use irox_networking::address::IPv4Network;
441 /// let loopback = IPv4Network::from_net_and_cidr(&[127,0,0,53], 32).unwrap();
442 /// assert_eq!(true, loopback.is_loopback());
443 ///
444 /// let quad_eight = IPv4Network::from_net_and_cidr(&[8,8,8,8], 32).unwrap();
445 /// assert_eq!(false, quad_eight.is_loopback());
446 /// ```
447 pub fn is_loopback(&self) -> bool {
448 let net = self.network_id.address;
449 net & 0xFF000000 == 0x7F000000
450 }
451
452 ///
453 /// Returns true if this network represents a carrier-grade NAT address, in `100.64.0.0/10`
454 ///
455 /// # Example
456 /// ```
457 /// # use irox_networking::address::IPv4Network;
458 /// let carrier_nat = IPv4Network::from_net_and_cidr(&[100,80,0,0], 12).unwrap();
459 /// assert_eq!(true, carrier_nat.is_shared_carrier_nat());
460 ///
461 /// let quad_eight = IPv4Network::from_net_and_cidr(&[8,8,8,8], 32).unwrap();
462 /// assert_eq!(false, quad_eight.is_shared_carrier_nat());
463 /// ```
464 pub fn is_shared_carrier_nat(&self) -> bool {
465 let net = self.network_id.address;
466 net & 0xFFC00000 == 0x64400000
467 }
468
469 pub fn host_in_network(&self, host: IPv4Address) -> bool {
470 let lower = host.address & self.host_mask;
471 let net = host.address & self.network_mask;
472 lower > 0 && lower != self.host_mask && self.network_id.address == net
473 }
474
475 pub fn get_network_address(&self) -> IPv4Address {
476 self.network_id
477 }
478
479 pub fn get_broadcast_address(&self) -> IPv4Address {
480 IPv4Address {
481 address: self.network_id.address | self.host_mask,
482 }
483 }
484}
485
486impl Display for IPv4Network {
487 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
488 f.write_fmt(format_args!("{}/{}", self.network_id, self.cidr))
489 }
490}
491
492#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
493pub struct IPv6Network {
494 pub(crate) network_id: u128,
495 pub(crate) network_mask: u128,
496}
497
498#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
499pub struct IPv6Address {
500 pub(crate) address: u128,
501}
502
503impl IPv6Address {
504 ///
505 /// # Example
506 /// ```
507 /// # use irox_networking::address::IPv6Address;
508 /// let addr = IPv6Address::new(&[0x2001,0x0DB8,0x85A3,0x0000,0x0000,0x8A2E,0x0370,0x7334]);
509 ///
510 /// assert_eq!("2001:db8:85a3::8a2e:370:7334", format!("{addr}"));
511 /// assert_eq!("2001:0db8:85a3:0000:0000:8a2e:0370:7334", format!("{addr:#}"));
512 ///
513 /// assert_eq!("::", format!("{}", IPv6Address::new(&[0,0,0,0,0,0,0,0])));
514 /// assert_eq!("::1", format!("{}", IPv6Address::new(&[0,0,0,0,0,0,0,1])));
515 ///
516 /// ```
517 pub fn new(val: &[u16; 8]) -> IPv6Address {
518 let address = u128::from_u16_array(val);
519 IPv6Address { address }
520 }
521}
522
523impl Display for IPv6Address {
524 fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
525 let bytes = self.address.to_u16_array();
526 if fmt.alternate() {
527 // full form, no collapse.
528 let [a, b, c, d, e, f, g, h] = bytes;
529 return fmt.write_fmt(format_args!(
530 "{a:04x}:{b:04x}:{c:04x}:{d:04x}:{e:04x}:{f:04x}:{g:04x}:{h:04x}"
531 ));
532 }
533 // collapsed form.
534 if let Some((longest_zeroes_point, num_zeroes)) = longest_consecutive_values(&bytes, &0) {
535 if longest_zeroes_point == 0 && num_zeroes == 8 {
536 return fmt.write_str("::");
537 }
538 if num_zeroes > 1 {
539 let bytes: Vec<String> = bytes
540 .iter()
541 .enumerate()
542 .filter_map(|(idx, val)| {
543 if idx == longest_zeroes_point || (idx == 1 && longest_zeroes_point == 0) {
544 return Some(String::new());
545 } else if idx > longest_zeroes_point
546 && idx < (longest_zeroes_point + num_zeroes)
547 {
548 return None;
549 }
550 Some(format!("{val:x}"))
551 })
552 .collect();
553 if bytes.is_empty() {
554 return fmt.write_str("::");
555 }
556 fmt.write_fmt(format_args!("{}", bytes.join(":")))?;
557 return Ok(());
558 }
559 }
560 let [a, b, c, d, e, f, g, h] = bytes;
561 fmt.write_fmt(format_args!(
562 "{a:x}:{b:x}:{c:x}:{d:x}:{e:x}:{f:x}:{g:x}:{h:x}"
563 ))
564 }
565}
566
567impl From<IPv6Address> for std::net::Ipv6Addr {
568 fn from(value: IPv6Address) -> Self {
569 std::net::Ipv6Addr::from(value.address)
570 }
571}
572impl From<&IPv6Address> for std::net::Ipv6Addr {
573 fn from(value: &IPv6Address) -> Self {
574 std::net::Ipv6Addr::from(value.address)
575 }
576}