Skip to main content

use_mac/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::str;
5
6/// Stores a parsed MAC address.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct MacAddress {
9    /// Raw MAC address octets.
10    pub octets: [u8; 6],
11}
12
13fn normalized_hex(input: &str) -> Option<String> {
14    let trimmed = input.trim();
15
16    if trimmed.is_empty() {
17        return None;
18    }
19
20    if trimmed.contains('.') {
21        let parts: Vec<_> = trimmed.split('.').collect();
22
23        if parts.len() != 3
24            || parts.iter().any(|part| {
25                part.len() != 4 || !part.chars().all(|character| character.is_ascii_hexdigit())
26            })
27        {
28            return None;
29        }
30
31        return Some(parts.concat().to_ascii_uppercase());
32    }
33
34    if trimmed.contains(':') || trimmed.contains('-') {
35        let separator = if trimmed.contains(':') { ':' } else { '-' };
36        let parts: Vec<_> = trimmed.split(separator).collect();
37
38        if parts.len() != 6
39            || parts.iter().any(|part| {
40                part.len() != 2 || !part.chars().all(|character| character.is_ascii_hexdigit())
41            })
42        {
43            return None;
44        }
45
46        return Some(parts.concat().to_ascii_uppercase());
47    }
48
49    if trimmed.len() == 12
50        && trimmed
51            .chars()
52            .all(|character| character.is_ascii_hexdigit())
53    {
54        Some(trimmed.to_ascii_uppercase())
55    } else {
56        None
57    }
58}
59
60/// Returns `true` when the input is a valid MAC address in a supported format.
61pub fn is_mac_address(input: &str) -> bool {
62    parse_mac_address(input).is_some()
63}
64
65/// Parses a MAC address from a common textual format.
66pub fn parse_mac_address(input: &str) -> Option<MacAddress> {
67    let hex = normalized_hex(input)?;
68    let mut octets = [0_u8; 6];
69
70    for (index, chunk) in hex.as_bytes().chunks(2).enumerate() {
71        let chunk = str::from_utf8(chunk).ok()?;
72        octets[index] = u8::from_str_radix(chunk, 16).ok()?;
73    }
74
75    Some(MacAddress { octets })
76}
77
78/// Formats a MAC address using colon separators.
79pub fn format_mac_colon(mac: &MacAddress) -> String {
80    mac.octets
81        .iter()
82        .map(|octet| format!("{octet:02X}"))
83        .collect::<Vec<_>>()
84        .join(":")
85}
86
87/// Formats a MAC address using hyphen separators.
88pub fn format_mac_hyphen(mac: &MacAddress) -> String {
89    mac.octets
90        .iter()
91        .map(|octet| format!("{octet:02X}"))
92        .collect::<Vec<_>>()
93        .join("-")
94}
95
96/// Normalizes a MAC address to uppercase colon-separated form.
97pub fn normalize_mac(input: &str) -> Option<String> {
98    parse_mac_address(input).map(|mac| format_mac_colon(&mac))
99}
100
101/// Returns `true` when the MAC address is the all-ones broadcast address.
102pub fn is_broadcast_mac(input: &str) -> bool {
103    parse_mac_address(input).is_some_and(|mac| mac.octets.iter().all(|octet| *octet == 0xFF))
104}
105
106/// Returns `true` when the MAC address is the all-zero address.
107pub fn is_zero_mac(input: &str) -> bool {
108    parse_mac_address(input).is_some_and(|mac| mac.octets.iter().all(|octet| *octet == 0x00))
109}