Skip to content

Commit

Permalink
Merge pull request #1456 from GuillaumeGomez/from-str
Browse files Browse the repository at this point in the history
Implement `FromStr` for `IpNetwork` and `MacAddr`
  • Loading branch information
GuillaumeGomez authored Jan 16, 2025
2 parents 949cb53 + 4804c14 commit 58ddac0
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 5 deletions.
142 changes: 138 additions & 4 deletions src/common/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use std::collections::HashMap;
use std::fmt;
use std::net::IpAddr;
use std::net::{AddrParseError, IpAddr};
use std::num::ParseIntError;
use std::str::FromStr;

use crate::{NetworkDataInner, NetworksInner};

Expand Down Expand Up @@ -433,14 +435,58 @@ impl fmt::Display for MacAddr {
}
}

/// Ip networks address for network interface.
/// Error type returned from `MacAddr::from_str` implementation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacAddrFromStrError {
/// A number is not in hexadecimal format.
IntError(ParseIntError),
/// Input is not of format `{02X}:{02X}:{02X}:{02X}:{02X}:{02X}`.
InvalidAddrFormat,
}

impl FromStr for MacAddr {
type Err = MacAddrFromStrError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s
.split(':')
.map(|s| u8::from_str_radix(s, 16).map_err(MacAddrFromStrError::IntError));

let Some(data0) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data1) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data2) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data3) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data4) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};
let Some(data5) = parts.next() else {
return Err(MacAddrFromStrError::InvalidAddrFormat);
};

if parts.next().is_some() {
return Err(MacAddrFromStrError::InvalidAddrFormat);
}

Ok(MacAddr([data0?, data1?, data2?, data3?, data4?, data5?]))
}
}

/// IP networks address for network interface.
///
/// It is returned by [`NetworkData::ip_networks`][crate::NetworkData::ip_networks].
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct IpNetwork {
/// The ip of the network interface
/// The IP of the network interface.
pub addr: IpAddr,
/// The netmask, prefix of the ipaddress
/// The netmask, prefix of the IP address.
pub prefix: u8,
}

Expand All @@ -450,10 +496,46 @@ impl fmt::Display for IpNetwork {
}
}

/// Error type returned from `MacAddr::from_str` implementation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpNetworkFromStrError {
/// Prefix is not an integer.
PrefixError(ParseIntError),
/// Failed to parse IP address.
AddrParseError(AddrParseError),
/// Input is not of format `[IP address]/[number]`.
InvalidAddrFormat,
}

impl FromStr for IpNetwork {
type Err = IpNetworkFromStrError;

#[allow(clippy::from_str_radix_10)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('/');

let Some(addr) = parts.next() else {
return Err(IpNetworkFromStrError::InvalidAddrFormat);
};
let Some(prefix) = parts.next() else {
return Err(IpNetworkFromStrError::InvalidAddrFormat);
};
if parts.next().is_some() {
return Err(IpNetworkFromStrError::InvalidAddrFormat);
}

Ok(IpNetwork {
addr: IpAddr::from_str(addr).map_err(IpNetworkFromStrError::AddrParseError)?,
prefix: u8::from_str_radix(prefix, 10).map_err(IpNetworkFromStrError::PrefixError)?,
})
}
}

#[cfg(test)]
mod tests {
use crate::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

// Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct
#[test]
Expand All @@ -476,6 +558,24 @@ mod tests {
assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified());
}

#[test]
fn check_mac_address_conversions() {
let mac = MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf]);

let mac_s = mac.to_string();
assert_eq!("0a:0b:0c:0d:0e:0f", mac_s);
assert_eq!(Ok(mac), MacAddr::from_str(&mac_s));

assert_eq!(
MacAddr::from_str("0a:0b:0c:0d:0e:0f:01"),
Err(MacAddrFromStrError::InvalidAddrFormat)
);
assert_eq!(
MacAddr::from_str("0a:0b:0c:0d:0e"),
Err(MacAddrFromStrError::InvalidAddrFormat)
);
}

// Ensure that the `Display` and `Debug` traits are implemented on the `IpNetwork` struct
#[test]
fn check_display_impl_ip_network_ipv4() {
Expand Down Expand Up @@ -518,4 +618,38 @@ mod tests {
}
panic!("Networks should have at least one IP network ");
}

#[test]
fn check_ip_network_conversions() {
let addr = IpNetwork {
addr: IpAddr::from(Ipv6Addr::new(0xff, 0xa, 0x8, 0x12, 0x7, 0xc, 0xa, 0xb)),
prefix: 12,
};

let addr_s = addr.to_string();
assert_eq!("ff:a:8:12:7:c:a:b/12", addr_s);
assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s));

let addr = IpNetwork {
addr: IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)),
prefix: 21,
};

let addr_s = addr.to_string();
assert_eq!("255.255.255.0/21", addr_s);
assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s));

assert_eq!(
IpNetwork::from_str("ff:a:8:12:7:c:a:b"),
Err(IpNetworkFromStrError::InvalidAddrFormat)
);
assert_eq!(
IpNetwork::from_str("ff:a:8:12:7:c:a:b/12/12"),
Err(IpNetworkFromStrError::InvalidAddrFormat)
);
match IpNetwork::from_str("0a:0b:0c:0d:0e/12") {
Err(IpNetworkFromStrError::AddrParseError(_)) => {}
x => panic!("expected `IpNetworkFromStrError::AddrParseError`, found {x:?}"),
}
}
}
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ pub use crate::common::component::{Component, Components};
#[cfg(feature = "disk")]
pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks};
#[cfg(feature = "network")]
pub use crate::common::network::{IpNetwork, MacAddr, NetworkData, Networks};
pub use crate::common::network::{
IpNetwork, IpNetworkFromStrError, MacAddr, MacAddrFromStrError, NetworkData, Networks,
};
#[cfg(feature = "system")]
pub use crate::common::system::{
get_current_pid, CGroupLimits, Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process,
Expand Down

0 comments on commit 58ddac0

Please sign in to comment.