Skip to main content

elicitation/verification/types/
macaddr.rs

1//! MAC address byte-level validation foundation.
2//!
3//! This module provides validated MAC address byte sequences (EUI-48).
4//! It forms the foundation for MAC address contract types.
5
6use super::ValidationError;
7
8// ============================================================================
9// MAC Address Structure (EUI-48 / IEEE 802)
10// ============================================================================
11//
12// 6 bytes (48 bits): XX:XX:XX:XX:XX:XX
13//
14// Byte 0, bit 0 (LSB): Unicast/Multicast
15//   - 0 = Unicast (individual address)
16//   - 1 = Multicast (group address)
17//
18// Byte 0, bit 1: Universal/Local
19//   - 0 = Universal (globally unique, assigned by IEEE)
20//   - 1 = Local (locally administered)
21//
22// Examples:
23//   - 00:1A:2B:3C:4D:5E (unicast, universal)
24//   - 01:00:5E:00:00:01 (multicast, universal - IPv4 multicast)
25//   - 02:1A:2B:3C:4D:5E (unicast, local)
26//   - FF:FF:FF:FF:FF:FF (broadcast - special multicast)
27
28// ============================================================================
29// Core MAC Address Type
30// ============================================================================
31
32/// A validated MAC address (6 bytes, EUI-48).
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct MacAddr {
35    octets: [u8; 6],
36}
37
38impl MacAddr {
39    /// Create a new MacAddr from octets.
40    ///
41    /// Always succeeds since all octet combinations are valid MAC addresses.
42    pub fn new(octets: [u8; 6]) -> Self {
43        Self { octets }
44    }
45
46    /// Get the octets.
47    pub fn octets(&self) -> [u8; 6] {
48        self.octets
49    }
50
51    /// Check if this is a unicast address (bit 0 of byte 0 is 0).
52    pub fn is_unicast(&self) -> bool {
53        (self.octets[0] & 0x01) == 0
54    }
55
56    /// Check if this is a multicast address (bit 0 of byte 0 is 1).
57    pub fn is_multicast(&self) -> bool {
58        (self.octets[0] & 0x01) == 1
59    }
60
61    /// Check if this is a universal address (bit 1 of byte 0 is 0).
62    pub fn is_universal(&self) -> bool {
63        (self.octets[0] & 0x02) == 0
64    }
65
66    /// Check if this is a locally administered address (bit 1 of byte 0 is 1).
67    pub fn is_local(&self) -> bool {
68        (self.octets[0] & 0x02) == 2
69    }
70
71    /// Check if this is the broadcast address (FF:FF:FF:FF:FF:FF).
72    pub fn is_broadcast(&self) -> bool {
73        self.octets == [0xFF; 6]
74    }
75
76    /// Check if this is a null address (00:00:00:00:00:00).
77    pub fn is_null(&self) -> bool {
78        self.octets == [0x00; 6]
79    }
80}
81
82// ============================================================================
83// Validation Functions
84// ============================================================================
85
86/// Check if MAC address is unicast (bit 0 of byte 0 is 0).
87pub fn is_unicast(octets: &[u8; 6]) -> bool {
88    (octets[0] & 0x01) == 0
89}
90
91/// Check if MAC address is multicast (bit 0 of byte 0 is 1).
92pub fn is_multicast(octets: &[u8; 6]) -> bool {
93    (octets[0] & 0x01) == 1
94}
95
96/// Check if MAC address is universal (bit 1 of byte 0 is 0).
97pub fn is_universal(octets: &[u8; 6]) -> bool {
98    (octets[0] & 0x02) == 0
99}
100
101/// Check if MAC address is locally administered (bit 1 of byte 0 is 1).
102pub fn is_local(octets: &[u8; 6]) -> bool {
103    (octets[0] & 0x02) == 2
104}
105
106// ============================================================================
107// Contract Types
108// ============================================================================
109
110/// A MAC address guaranteed to be unicast.
111///
112/// Unicast addresses are individual device addresses (bit 0 of byte 0 is 0).
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114pub struct MacUnicast(MacAddr);
115
116impl MacUnicast {
117    /// Create a new MacUnicast, validating it's a unicast address.
118    ///
119    /// # Errors
120    ///
121    /// Returns `ValidationError::NotUnicastMac` if multicast.
122    pub fn new(octets: [u8; 6]) -> Result<Self, ValidationError> {
123        let mac = MacAddr::new(octets);
124        if mac.is_unicast() {
125            Ok(Self(mac))
126        } else {
127            Err(ValidationError::NotUnicastMac)
128        }
129    }
130
131    /// Get the underlying MacAddr.
132    pub fn get(&self) -> &MacAddr {
133        &self.0
134    }
135
136    /// Get the octets.
137    pub fn octets(&self) -> [u8; 6] {
138        self.0.octets()
139    }
140
141    /// Unwrap into the underlying MacAddr.
142    pub fn into_inner(self) -> MacAddr {
143        self.0
144    }
145}
146
147/// A MAC address guaranteed to be multicast.
148///
149/// Multicast addresses are group addresses (bit 0 of byte 0 is 1).
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
151pub struct MacMulticast(MacAddr);
152
153impl MacMulticast {
154    /// Create a new MacMulticast, validating it's a multicast address.
155    ///
156    /// # Errors
157    ///
158    /// Returns `ValidationError::NotMulticastMac` if unicast.
159    pub fn new(octets: [u8; 6]) -> Result<Self, ValidationError> {
160        let mac = MacAddr::new(octets);
161        if mac.is_multicast() {
162            Ok(Self(mac))
163        } else {
164            Err(ValidationError::NotMulticastMac)
165        }
166    }
167
168    /// Get the underlying MacAddr.
169    pub fn get(&self) -> &MacAddr {
170        &self.0
171    }
172
173    /// Get the octets.
174    pub fn octets(&self) -> [u8; 6] {
175        self.0.octets()
176    }
177
178    /// Unwrap into the underlying MacAddr.
179    pub fn into_inner(self) -> MacAddr {
180        self.0
181    }
182}
183
184/// A MAC address guaranteed to be universal (IEEE assigned).
185///
186/// Universal addresses have globally unique OUIs (bit 1 of byte 0 is 0).
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub struct MacUniversal(MacAddr);
189
190impl MacUniversal {
191    /// Create a new MacUniversal, validating it's a universal address.
192    ///
193    /// # Errors
194    ///
195    /// Returns `ValidationError::NotUniversalMac` if locally administered.
196    pub fn new(octets: [u8; 6]) -> Result<Self, ValidationError> {
197        let mac = MacAddr::new(octets);
198        if mac.is_universal() {
199            Ok(Self(mac))
200        } else {
201            Err(ValidationError::NotUniversalMac)
202        }
203    }
204
205    /// Get the underlying MacAddr.
206    pub fn get(&self) -> &MacAddr {
207        &self.0
208    }
209
210    /// Get the octets.
211    pub fn octets(&self) -> [u8; 6] {
212        self.0.octets()
213    }
214
215    /// Unwrap into the underlying MacAddr.
216    pub fn into_inner(self) -> MacAddr {
217        self.0
218    }
219}
220
221/// A MAC address guaranteed to be locally administered.
222///
223/// Locally administered addresses are user-configurable (bit 1 of byte 0 is 1).
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
225pub struct MacLocal(MacAddr);
226
227impl MacLocal {
228    /// Create a new MacLocal, validating it's a locally administered address.
229    ///
230    /// # Errors
231    ///
232    /// Returns `ValidationError::NotLocalMac` if universal.
233    pub fn new(octets: [u8; 6]) -> Result<Self, ValidationError> {
234        let mac = MacAddr::new(octets);
235        if mac.is_local() {
236            Ok(Self(mac))
237        } else {
238            Err(ValidationError::NotLocalMac)
239        }
240    }
241
242    /// Get the underlying MacAddr.
243    pub fn get(&self) -> &MacAddr {
244        &self.0
245    }
246
247    /// Get the octets.
248    pub fn octets(&self) -> [u8; 6] {
249        self.0.octets()
250    }
251
252    /// Unwrap into the underlying MacAddr.
253    pub fn into_inner(self) -> MacAddr {
254        self.0
255    }
256}
257
258// ============================================================================
259// Helper Functions
260// ============================================================================
261
262/// Format MAC address as XX:XX:XX:XX:XX:XX.
263
264// ============================================================================
265// Tests
266// ============================================================================
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn test_unicast_detection() {
274        // Even first byte = unicast
275        let unicast = [0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E];
276        assert!(is_unicast(&unicast));
277        assert!(!is_multicast(&unicast));
278
279        let mac = MacAddr::new(unicast);
280        assert!(mac.is_unicast());
281        assert!(!mac.is_multicast());
282
283        let unicast_type = MacUnicast::new(unicast);
284        assert!(unicast_type.is_ok());
285    }
286
287    #[test]
288    fn test_multicast_detection() {
289        // Odd first byte = multicast
290        let multicast = [0x01, 0x00, 0x5E, 0x00, 0x00, 0x01];
291        assert!(is_multicast(&multicast));
292        assert!(!is_unicast(&multicast));
293
294        let mac = MacAddr::new(multicast);
295        assert!(mac.is_multicast());
296        assert!(!mac.is_unicast());
297
298        let multicast_type = MacMulticast::new(multicast);
299        assert!(multicast_type.is_ok());
300    }
301
302    #[test]
303    fn test_broadcast() {
304        let broadcast = [0xFF; 6];
305        let mac = MacAddr::new(broadcast);
306        assert!(mac.is_broadcast());
307        assert!(mac.is_multicast()); // Broadcast is a special multicast
308    }
309
310    #[test]
311    fn test_null_address() {
312        let null = [0x00; 6];
313        let mac = MacAddr::new(null);
314        assert!(mac.is_null());
315        assert!(mac.is_unicast()); // Null is unicast
316    }
317
318    #[test]
319    fn test_universal_detection() {
320        // Bit 1 clear = universal
321        let universal = [0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E];
322        assert!(is_universal(&universal));
323        assert!(!is_local(&universal));
324
325        let mac = MacAddr::new(universal);
326        assert!(mac.is_universal());
327        assert!(!mac.is_local());
328
329        let universal_type = MacUniversal::new(universal);
330        assert!(universal_type.is_ok());
331    }
332
333    #[test]
334    fn test_local_detection() {
335        // Bit 1 set = local
336        let local = [0x02, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E];
337        assert!(is_local(&local));
338        assert!(!is_universal(&local));
339
340        let mac = MacAddr::new(local);
341        assert!(mac.is_local());
342        assert!(!mac.is_universal());
343
344        let local_type = MacLocal::new(local);
345        assert!(local_type.is_ok());
346    }
347
348    #[test]
349    fn test_unicast_rejects_multicast() {
350        let multicast = [0x01, 0x00, 0x5E, 0x00, 0x00, 0x01];
351        let result = MacUnicast::new(multicast);
352        assert!(result.is_err());
353    }
354
355    #[test]
356    fn test_multicast_rejects_unicast() {
357        let unicast = [0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E];
358        let result = MacMulticast::new(unicast);
359        assert!(result.is_err());
360    }
361
362    #[test]
363    fn test_combined_bits() {
364        // Both bits set: local multicast (0x03)
365        let local_multicast = [0x03, 0x00, 0x00, 0x00, 0x00, 0x01];
366        let mac = MacAddr::new(local_multicast);
367        assert!(mac.is_multicast());
368        assert!(mac.is_local());
369
370        // Only bit 1 set: local unicast (0x02)
371        let local_unicast = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
372        let mac = MacAddr::new(local_unicast);
373        assert!(mac.is_unicast());
374        assert!(mac.is_local());
375
376        // Only bit 0 set: universal multicast (0x01)
377        let universal_multicast = [0x01, 0x00, 0x5E, 0x00, 0x00, 0x01];
378        let mac = MacAddr::new(universal_multicast);
379        assert!(mac.is_multicast());
380        assert!(mac.is_universal());
381    }
382}