libwifi/frame/components/
mac_address.rs

1use std::fmt;
2use std::hash::Hash;
3
4use rand::{Rng, RngCore, rng};
5
6/// This is our representation of a MAC-address
7///
8/// ```
9/// use libwifi::frame::components::MacAddress;
10///
11/// let address = MacAddress([255, 255, 255, 255, 255, 255]);
12/// println!("{}", address.is_broadcast());
13/// // -> true
14/// ```
15///
16#[derive(Clone, Debug, Hash, Eq, PartialEq, Copy, Ord, PartialOrd)]
17pub struct MacAddress(pub [u8; 6]);
18
19impl MacAddress {
20    pub fn from_vec(vec: Vec<u8>) -> Option<MacAddress> {
21        if vec.len() == 6 {
22            let mut arr = [0u8; 6];
23            for (place, element) in arr.iter_mut().zip(vec.iter()) {
24                *place = *element;
25            }
26            Some(MacAddress(arr))
27        } else {
28            // Return None if the Vec is not exactly 6 bytes long
29            None
30        }
31    }
32
33    /// Generate u64.
34    pub fn to_u64(&self) -> u64 {
35        let bytes = self.0;
36        ((bytes[0] as u64) << 40)
37            | ((bytes[1] as u64) << 32)
38            | ((bytes[2] as u64) << 24)
39            | ((bytes[3] as u64) << 16)
40            | ((bytes[4] as u64) << 8)
41            | (bytes[5] as u64)
42    }
43
44    /// Generate string with delimitters.
45    pub fn to_long_string(&self) -> String {
46        format!(
47            "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
48            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5],
49        )
50    }
51
52    /// Generate random valid mac
53    pub fn random() -> Self {
54        loop {
55            let mac = MacAddress(generate_random_bytes(6).try_into().unwrap());
56            if mac.is_real_device() {
57                return mac;
58            }
59        }
60    }
61
62    pub fn broadcast() -> Self {
63        MacAddress([255, 255, 255, 255, 255, 255])
64    }
65
66    pub fn zeroed() -> Self {
67        MacAddress([0, 0, 0, 0, 0, 0])
68    }
69
70    /// Generate a random MAC address using the same OUI as the given MAC address
71    pub fn random_with_oui(other: &MacAddress) -> Self {
72        let mut rng = rand::rng();
73        let mut new_mac = other.0;
74        new_mac[3..6].fill_with(|| rng.random());
75        MacAddress(new_mac)
76    }
77
78    /// Encode mac address for network.
79    pub fn encode(&self) -> [u8; 6] {
80        self.0
81    }
82
83    /// Check if this is a private address (locally set bit)
84    pub fn is_private(&self) -> bool {
85        self.0[0] & 0x02 != 0
86    }
87
88    /// Check if this is a multicast address
89    pub fn is_mcast(&self) -> bool {
90        self.0[0] % 2 == 1
91    }
92
93    /// Check whether this MAC addresses the whole network.
94    pub fn is_broadcast(&self) -> bool {
95        self.0 == [255, 255, 255, 255, 255, 255]
96    }
97
98    /// Check whether this is a group address.
99    /// Group addresses start with 01:80:C2::0/24.
100    pub fn is_groupcast(&self) -> bool {
101        self.0[0] == 1 && self.0[1] == 128 && self.0[2] == 194
102    }
103
104    /// The 01:00:5e::0/18 space is reserved for ipv4 multicast
105    pub fn is_ipv4_multicast(&self) -> bool {
106        self.0[0] == 1 && self.0[1] == 0 && self.0[2] == 94
107    }
108
109    /// 33:33::0/24 is used for ipv6 neighborhood discovery.
110    pub fn is_ipv6_neighborhood_discovery(&self) -> bool {
111        self.0 == [51, 51, 0, 0, 0, 0]
112    }
113
114    /// The 33:33::0/24 space is reserved for ipv6 multicast
115    pub fn is_ipv6_multicast(&self) -> bool {
116        self.0[0] == 51 && self.0[1] == 51
117    }
118
119    /// The 01:80:c2::0/18 space is reserved for spanning-tree requests.
120    pub fn is_spanning_tree(&self) -> bool {
121        self.0[0] == 1 && self.0[1] == 128 && self.0[2] == 194
122    }
123
124    /// A helper function to check whether the mac address is an actual device or just some kind of
125    /// "meta" mac address.
126    ///
127    /// This function is most likely not complete, but it already covers a cases.
128    pub fn is_real_device(&self) -> bool {
129        !(self.is_ipv6_multicast()
130            || self.is_broadcast()
131            || self.is_ipv4_multicast()
132            || self.is_groupcast()
133            || self.is_spanning_tree()
134            || self.is_mcast())
135    }
136}
137
138impl fmt::Display for MacAddress {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        write!(
141            f,
142            "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
143            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5],
144        )
145    }
146}
147
148#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
149pub enum MacParseError {
150    InvalidDigit,
151    InvalidLength,
152}
153
154impl fmt::Display for MacParseError {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "Encountered an error while parsing a mac address.")
157    }
158}
159
160impl std::error::Error for MacParseError {}
161
162/// Parse a string mac representation.
163///
164/// ```
165/// use libwifi::frame::components::MacAddress;
166/// use std::str::FromStr;
167///
168/// let mac = MacAddress([12, 157, 146, 197, 170, 127]);
169///
170/// let colon = MacAddress::from_str("0c:9d:92:c5:aa:7f").unwrap();
171/// assert_eq!(colon, mac);
172///
173/// let dash = MacAddress::from_str("0c-9d-92-c5-aa-7f").unwrap();
174/// assert_eq!(dash, mac);
175///
176/// let no_delimiter = MacAddress::from_str("0c9d92c5aa7f").unwrap();
177/// assert_eq!(no_delimiter, mac);
178/// ```
179impl std::str::FromStr for MacAddress {
180    type Err = MacParseError;
181
182    fn from_str(input: &str) -> Result<Self, Self::Err> {
183        let mut array = [0u8; 6];
184
185        let input_lower = input.to_lowercase();
186        // Check if the input contains colons, and split accordingly
187        let bytes: Vec<&str> = if input_lower.contains(':') {
188            input_lower.split(':').collect()
189        } else if input.contains('-') {
190            input_lower.split('-').collect()
191        } else if input_lower.len() == 12 {
192            // If the input doesn't contain colons and is 12 characters long
193            input_lower
194                .as_bytes()
195                .chunks(2)
196                .map(|chunk| std::str::from_utf8(chunk).unwrap_or(""))
197                .collect()
198        } else {
199            return Err(MacParseError::InvalidLength);
200        };
201
202        // Validate the number of bytes
203        if bytes.len() != 6 {
204            return Err(MacParseError::InvalidLength);
205        }
206
207        // Parse each byte
208        for (count, byte) in bytes.iter().enumerate() {
209            array[count] = u8::from_str_radix(byte, 16).map_err(|_| MacParseError::InvalidDigit)?;
210        }
211
212        Ok(MacAddress(array))
213    }
214}
215
216pub fn generate_random_bytes(x: usize) -> Vec<u8> {
217    let mut rng = rng();
218    let length = x;
219    let mut bytes = vec![0u8; length];
220    rng.fill_bytes(&mut bytes);
221    // Ensure the first byte is even
222    if !bytes.is_empty() {
223        bytes[0] &= 0xFE; // 0xFE is 11111110 in binary
224    }
225
226    bytes
227}
228
229// We need to be able to glob against mac addresses, so a MacAddressGlob will be a mac address that we can do a match against.
230
231#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
232pub struct MacAddressGlob {
233    pattern: [u8; 6],
234    mask: [u8; 6],
235}
236
237impl fmt::Display for MacAddressGlob {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        for i in 0..6 {
240            if self.mask[i] == 0 {
241                write!(f, "*")?;
242            } else if self.mask[i] == 0xF0 {
243                write!(f, "{:x}*", self.pattern[i] >> 4)?;
244            } else {
245                write!(f, "{:02x}", self.pattern[i])?;
246            }
247            if i < 5 {
248                write!(f, ":")?;
249            }
250        }
251        Ok(())
252    }
253}
254
255pub enum MacGlobParseError {
256    InvalidDigit,
257    InvalidLength,
258}
259
260impl fmt::Display for MacGlobParseError {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        match self {
263            MacGlobParseError::InvalidDigit => write!(f, "Invalid hex digit in pattern"),
264            MacGlobParseError::InvalidLength => write!(f, "Pattern is too long"),
265        }
266    }
267}
268
269impl MacAddressGlob {
270    pub fn new(pattern: &str) -> Result<Self, MacGlobParseError> {
271        let normalized_pattern = pattern.to_lowercase().replace(&[':', '-', '.'][..], "");
272
273        if normalized_pattern.len() > 12 {
274            return Err(MacGlobParseError::InvalidLength);
275        }
276
277        let mut pattern_bytes = [0u8; 6];
278        let mut mask_bytes = [0u8; 6];
279
280        for (i, chunk) in normalized_pattern.as_bytes().chunks(2).enumerate() {
281            if i >= 6 {
282                return Err(MacGlobParseError::InvalidLength);
283            }
284            let (pattern_byte, mask_byte) = match chunk {
285                [b'*'] => (0x00, 0x00),
286                [high, b'*'] => {
287                    let high_nibble = match high {
288                        b'0'..=b'9' => high - b'0',
289                        b'a'..=b'f' => high - b'a' + 10,
290                        _ => return Err(MacGlobParseError::InvalidDigit),
291                    };
292                    (high_nibble << 4, 0xF0)
293                }
294                [high, low] => {
295                    let high_nibble = match high {
296                        b'0'..=b'9' => high - b'0',
297                        b'a'..=b'f' => high - b'a' + 10,
298                        _ => return Err(MacGlobParseError::InvalidDigit),
299                    };
300                    let low_nibble = match low {
301                        b'0'..=b'9' => low - b'0',
302                        b'a'..=b'f' => low - b'a' + 10,
303                        _ => return Err(MacGlobParseError::InvalidDigit),
304                    };
305                    ((high_nibble << 4) | low_nibble, 0xFF)
306                }
307                _ => return Err(MacGlobParseError::InvalidDigit),
308            };
309            pattern_bytes[i] = pattern_byte;
310            mask_bytes[i] = mask_byte;
311        }
312
313        // Handle patterns that are not full 6 bytes by filling remaining bytes with wildcards
314        for i in (normalized_pattern.len() / 2)..6 {
315            pattern_bytes[i] = 0x00;
316            mask_bytes[i] = 0x00;
317        }
318
319        Ok(Self {
320            pattern: pattern_bytes,
321            mask: mask_bytes,
322        })
323    }
324
325    pub fn from_mac_address(mac: &MacAddress) -> Self {
326        Self {
327            pattern: mac.0,
328            mask: [0xFF; 6],
329        }
330    }
331
332    pub fn matches(&self, mac: &MacAddress) -> bool {
333        for i in 0..6 {
334            if (mac.0[i] & self.mask[i]) != (self.pattern[i] & self.mask[i]) {
335                return false;
336            }
337        }
338        true
339    }
340}