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}