ieee80211/
mac_addr.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::Bssid;
6use anyhow::{format_err, Error};
7use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
8use std::fmt;
9use std::str::FromStr;
10use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
11
12// Strictly speaking, the MAC address is not defined in 802.11, but it's defined
13// here for convenience.
14pub(crate) type MacAddrByteArray = [u8; fidl_ieee80211::MAC_ADDR_LEN as usize];
15
16pub const BROADCAST_ADDR: MacAddr = MacAddr([0xFF; 6]);
17pub const NULL_ADDR: MacAddr = MacAddr([0x00; fidl_ieee80211::MAC_ADDR_LEN as usize]);
18
19#[repr(transparent)]
20#[derive(
21    KnownLayout,
22    FromBytes,
23    IntoBytes,
24    Immutable,
25    Unaligned,
26    Clone,
27    Copy,
28    PartialEq,
29    Eq,
30    PartialOrd,
31    Ord,
32    Hash,
33)]
34pub struct MacAddr(pub(crate) MacAddrByteArray);
35
36impl MacAddr {
37    pub const fn len(&self) -> usize {
38        self.0.len()
39    }
40
41    /// A MAC address is a unicast address if the least significant bit of the first octet is 0.
42    /// See "individual/group bit" in
43    /// https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/macgrp.pdf
44    pub fn is_unicast(&self) -> bool {
45        self.0[0] & 1 == 0
46    }
47
48    /// IEEE Std 802.3-2015, 3.2.3: The least significant bit of the first octet of a MAC address
49    /// denotes multicast.
50    pub fn is_multicast(&self) -> bool {
51        self.0[0] & 0x01 != 0
52    }
53
54    pub fn as_slice(&self) -> &[u8] {
55        &self.0
56    }
57}
58
59/// This trait aims to add some friction to convert a type into MacAddrBytes. The purpose being that
60/// function using the types implementing this trait, e.g. MacAddr, should prefer not accessing
61/// the MacAddrBytes directly when possible.
62pub trait MacAddrBytes {
63    fn to_array(&self) -> MacAddrByteArray;
64    fn as_array(&self) -> &MacAddrByteArray;
65}
66
67impl MacAddrBytes for MacAddr {
68    fn to_array(&self) -> MacAddrByteArray {
69        self.0
70    }
71
72    fn as_array(&self) -> &MacAddrByteArray {
73        &self.0
74    }
75}
76
77pub(crate) trait MacFmt {
78    fn to_mac_string(&self) -> String
79    where
80        Self: MacAddrBytes,
81    {
82        let mac = self.to_array();
83        format!(
84            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
85            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
86        )
87    }
88}
89
90pub trait OuiFmt {
91    fn to_oui_uppercase(&self, sep: &str) -> String
92    where
93        Self: MacAddrBytes,
94    {
95        let mac = self.to_array();
96        format!("{:02X}{}{:02X}{}{:02X}", mac[0], sep, mac[1], sep, mac[2])
97    }
98}
99
100impl MacFmt for MacAddr {}
101impl OuiFmt for MacAddr {}
102
103impl From<Bssid> for MacAddr {
104    fn from(bssid: Bssid) -> MacAddr {
105        MacAddr(bssid.0)
106    }
107}
108
109impl From<MacAddrByteArray> for MacAddr {
110    fn from(bytes: MacAddrByteArray) -> MacAddr {
111        MacAddr(bytes)
112    }
113}
114
115impl fmt::Display for MacAddr {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        write!(f, "{}", self.to_mac_string())
118    }
119}
120
121fn detect_delimiter(s: &str) -> Result<char, Error> {
122    let contains_semicolon = s.contains(':');
123    let contains_hyphen = s.contains('-');
124    match (contains_semicolon, contains_hyphen) {
125        (true, true) => return Err(format_err!("Either exclusively ':' or '-' must be used.")),
126        (false, false) => {
127            return Err(format_err!("No valid delimiter found. Only ':' and '-' are supported."))
128        }
129        (true, false) => Ok(':'),
130        (false, true) => Ok('-'),
131    }
132}
133
134impl FromStr for MacAddr {
135    type Err = Error;
136
137    fn from_str(s: &str) -> Result<Self, Self::Err> {
138        let mut bytes: MacAddrByteArray = [0; 6];
139        let mut index = 0;
140
141        let delimiter = detect_delimiter(s)?;
142        for octet in s.split(delimiter) {
143            if index == 6 {
144                return Err(format_err!("Too many octets"));
145            }
146            bytes[index] = u8::from_str_radix(octet, 16)?;
147            index += 1;
148        }
149
150        if index != 6 {
151            return Err(format_err!("Too few octets. Mixed delimiters are not supported."));
152        }
153        Ok(MacAddr(bytes))
154    }
155}
156
157impl fmt::Debug for MacAddr {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(f, "MacAddr({})", self)
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn format_mac_addr_as_mac_string() {
169        let mac_addr: MacAddr = MacAddr::from([0x00, 0x12, 0x48, 0x9a, 0xbc, 0xdf]);
170        assert_eq!("00:12:48:9a:bc:df", &format!("{}", mac_addr));
171    }
172
173    #[test]
174    fn format_mac_addr_as_mac_debug_string() {
175        let mac_addr: MacAddr = MacAddr::from([0x00, 0x12, 0x48, 0x9a, 0xbc, 0xdf]);
176        assert_eq!("MacAddr(00:12:48:9a:bc:df)", &format!("{:?}", mac_addr));
177    }
178
179    #[test]
180    fn format_oui_uppercase() {
181        let mac: MacAddr = MacAddr::from([0x0a, 0xb1, 0xcd, 0x9a, 0xbc, 0xdf]);
182        assert_eq!(mac.to_oui_uppercase(""), "0AB1CD");
183        assert_eq!(mac.to_oui_uppercase(":"), "0A:B1:CD");
184        assert_eq!(mac.to_oui_uppercase("-"), "0A-B1-CD");
185    }
186
187    #[test]
188    fn unicast_addresses() {
189        assert!(MacAddr::from([0; 6]).is_unicast());
190        assert!(MacAddr::from([0xfe; 6]).is_unicast());
191    }
192
193    #[test]
194    fn non_unicast_addresses() {
195        assert!(!MacAddr::from([0xff; 6]).is_unicast()); // broadcast
196        assert!(!MacAddr::from([0x33, 0x33, 0, 0, 0, 0]).is_unicast()); // IPv6 multicast
197        assert!(!MacAddr::from([0x01, 0x00, 0x53, 0, 0, 0]).is_unicast()); // IPv4 multicast
198    }
199
200    #[test]
201    fn is_multicast_valid_addr() {
202        assert!(MacAddr::from([33, 33, 33, 33, 33, 33]).is_multicast());
203    }
204
205    #[test]
206    fn is_multicast_not_valid_addr() {
207        assert!(!MacAddr::from([34, 33, 33, 33, 33, 33]).is_multicast());
208    }
209
210    #[test]
211    fn successfully_parse_mac_str() {
212        assert_eq!(
213            "01:23:cd:11:11:11".parse::<MacAddr>().unwrap(),
214            MacAddr::from([0x01, 0x23, 0xcd, 0x11, 0x11, 0x11])
215        );
216        assert_eq!(
217            "01-23-cd-11-11-11".parse::<MacAddr>().unwrap(),
218            MacAddr::from([0x01, 0x23, 0xcd, 0x11, 0x11, 0x11])
219        );
220        assert_eq!(
221            "1-23-cd-11-11-11".parse::<MacAddr>().unwrap(),
222            MacAddr::from([0x01, 0x23, 0xcd, 0x11, 0x11, 0x11])
223        );
224    }
225
226    #[test]
227    fn mac_addr_from_str() {
228        assert_eq!(
229            MacAddr::from_str("01:02:03:ab:cd:ef").unwrap(),
230            MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef])
231        );
232        assert_eq!(
233            MacAddr::from_str("01-02-03-ab-cd-ef").unwrap(),
234            MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef])
235        );
236    }
237
238    #[test]
239    fn fail_to_parse_mac_str() {
240        assert!("11:11:23::11:11:11".parse::<MacAddr>().is_err());
241        assert!("11:11:23:11:11:11:11".parse::<MacAddr>().is_err());
242        assert!(":11:23:11:11:11:11".parse::<MacAddr>().is_err());
243        assert!("11:23:11:11:11:11:11".parse::<MacAddr>().is_err());
244        assert!("11:23:11:11:11:11:".parse::<MacAddr>().is_err());
245        assert!("111:23:11:11:11:11".parse::<MacAddr>().is_err());
246        assert!("11:23-11:11-11:11".parse::<MacAddr>().is_err());
247        assert!("11-23:11-11:11-11".parse::<MacAddr>().is_err());
248        assert!("-11-23-11-11-11-11".parse::<MacAddr>().is_err());
249        assert!("11-23-11-11-11-11-".parse::<MacAddr>().is_err());
250    }
251}