Skip to main content

hardware_address/
eui64.rs

1addr_ty!(
2  /// Represents a physical EUI-64 format address.
3  Eui64Addr[8]
4);
5
6#[cfg(test)]
7mod tests {
8  use super::*;
9  use crate::{ParseError, TestCase};
10
11  use std::{string::ToString, vec, vec::Vec};
12
13  const EUI64_ADDRESS_SIZE: usize = 8;
14
15  fn test_cases() -> Vec<TestCase<EUI64_ADDRESS_SIZE>> {
16    vec![
17      // RFC 7042, Section 2.2.2
18      TestCase {
19        input: "02:00:5e:10:00:00:00:01",
20        output: Some(vec![0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01]),
21        err: None,
22      },
23      TestCase {
24        input: "02-00-5e-10-00-00-00-01",
25        output: Some(vec![0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01]),
26        err: None,
27      },
28      TestCase {
29        input: "0200.5e10.0000.0001",
30        output: Some(vec![0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01]),
31        err: None,
32      },
33      TestCase {
34        input: "ab:cd:ef:AB:CD:EF:ab:cd",
35        output: Some(vec![0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd]),
36        err: None,
37      },
38      TestCase {
39        input: "0200-5e10.0000.0001",
40        output: None,
41        err: Some(ParseError::UnexpectedSeparator {
42          expected: b'.',
43          actual: b'-',
44        }),
45      },
46      TestCase {
47        input: "xx00.5e10.0000.0001",
48        output: None,
49        err: Some(ParseError::InvalidHexDigit([b'x', b'x'])),
50      },
51      TestCase {
52        input: "00xx.5e10.0000.0001",
53        output: None,
54        err: Some(ParseError::InvalidHexDigit([b'x', b'x'])),
55      },
56    ]
57  }
58
59  #[test]
60  fn parse() {
61    let cases = test_cases();
62    for (i, test) in cases.iter().enumerate() {
63      let result = Eui64Addr::try_from(test.input);
64
65      match (result, &test.output) {
66        (Ok(out), Some(expected)) => {
67          assert_eq!(
68            out,
69            expected.as_slice(),
70            "Test case {}: Eui64Addr::parse({}) output mismatch",
71            i,
72            test.input
73          );
74
75          // Test round-trip if this was a valid case
76          if test.err.is_none() {
77            let formatted = out.to_string();
78            let round_trip = Eui64Addr::try_from(formatted.as_str());
79            assert!(
80              round_trip.is_ok(),
81              "Test case {}: Round-trip parse failed for {}",
82              i,
83              formatted
84            );
85            assert_eq!(
86              round_trip.unwrap(),
87              out,
88              "Test case {}: Round-trip value mismatch",
89              i
90            );
91          }
92        }
93        (Err(err), None) => {
94          assert_eq!(
95            Some(&err),
96            test.err.as_ref(),
97            "Test case {}: Expected error containing '{:?}', got '{:?}'",
98            i,
99            test.err,
100            err
101          );
102        }
103        (Ok(out), None) => {
104          panic!(
105            "Test case {}: Expected error '{:?}', got success: {:?}",
106            i, test.err, out
107          );
108        }
109        (Err(err), Some(expected)) => {
110          panic!(
111            "Test case {}: Expected {:?}, got error: {:?}",
112            i, expected, err
113          );
114        }
115      }
116    }
117  }
118
119  #[test]
120  fn test_default() {
121    let addr = Eui64Addr::default();
122    assert_eq!(addr.octets(), [0; EUI64_ADDRESS_SIZE]);
123  }
124
125  #[test]
126  fn formatted() {
127    let addr = Eui64Addr::try_from("02:00:5e:10:00:00:00:01").unwrap();
128    assert_eq!(addr.to_string(), "02:00:5e:10:00:00:00:01");
129    assert_eq!(addr.to_colon_separated(), "02:00:5e:10:00:00:00:01");
130
131    let dot = addr.to_dot_separated_array();
132    let dot_str = core::str::from_utf8(&dot).unwrap();
133    assert_eq!(dot_str, "0200.5e10.0000.0001");
134    assert_eq!(addr.to_dot_separated(), "0200.5e10.0000.0001");
135
136    let dashed = addr.to_hyphen_separated_array();
137    let dashed_str = core::str::from_utf8(&dashed).unwrap();
138    assert_eq!(dashed_str, "02-00-5e-10-00-00-00-01");
139    assert_eq!(addr.to_hyphen_separated(), "02-00-5e-10-00-00-00-01");
140  }
141
142  #[cfg(feature = "serde")]
143  #[test]
144  fn serde_human_readable() {
145    let addr = Eui64Addr::try_from("02:00:5e:10:00:00:00:01").unwrap();
146    let json = serde_json::to_string(&addr).unwrap();
147    assert_eq!(json, "\"02:00:5e:10:00:00:00:01\"");
148
149    let addr2: Eui64Addr = serde_json::from_str(&json).unwrap();
150    assert_eq!(addr, addr2);
151  }
152
153  #[cfg(feature = "serde")]
154  #[test]
155  fn serde_human_unreadable() {
156    let addr = Eui64Addr::try_from("02:00:5e:10:00:00:00:01").unwrap();
157    let encoded = bincode::serde::encode_to_vec(addr, bincode::config::standard()).unwrap();
158    assert_eq!(encoded, [2, 0, 94, 16, 0, 0, 0, 1]);
159    assert_eq!(addr.octets(), [2, 0, 94, 16, 0, 0, 0, 1]);
160
161    let addr2: Eui64Addr = bincode::serde::decode_from_slice(&encoded, bincode::config::standard())
162      .unwrap()
163      .0;
164    assert_eq!(addr, addr2);
165
166    let addr3 = Eui64Addr::from([2, 0, 94, 16, 0, 0, 0, 1]);
167    assert_eq!(addr, addr3);
168
169    let octets: [u8; EUI64_ADDRESS_SIZE] = addr3.into();
170    assert_eq!(octets, addr3.octets());
171    println!("{:?}", addr);
172  }
173
174  // ------------------------------------------------------------------
175  // `arbitrary` / `quickcheck` Arbitrary impls
176  // ------------------------------------------------------------------
177
178  #[cfg(feature = "arbitrary")]
179  #[test]
180  fn arbitrary_is_deterministic() {
181    use arbitrary::{Arbitrary, Unstructured};
182
183    let data = [0xAA; 32];
184    let a = Eui64Addr::arbitrary(&mut Unstructured::new(&data)).expect("arbitrary should succeed");
185    let b = Eui64Addr::arbitrary(&mut Unstructured::new(&data)).expect("arbitrary should succeed");
186    assert_eq!(a, b, "arbitrary should be deterministic for a fixed input");
187  }
188
189  #[cfg(feature = "arbitrary")]
190  #[test]
191  fn arbitrary_size_hint_matches_byte_array() {
192    use arbitrary::Arbitrary;
193
194    let hint = Eui64Addr::size_hint(0);
195    let expected = <[u8; EUI64_ADDRESS_SIZE] as Arbitrary>::size_hint(0);
196    assert_eq!(hint, expected);
197  }
198
199  #[cfg(feature = "arbitrary")]
200  #[test]
201  fn arbitrary_consumes_expected_bytes() {
202    use arbitrary::{Arbitrary, Unstructured};
203
204    // Two 8-byte draws should fit in a 16-byte buffer.
205    let data = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
206    let mut u = Unstructured::new(&data);
207    let _first = Eui64Addr::arbitrary(&mut u).unwrap();
208    let _second = Eui64Addr::arbitrary(&mut u).unwrap();
209  }
210
211  #[cfg(feature = "quickcheck")]
212  #[test]
213  fn quickcheck_arbitrary_roundtrips_through_string() {
214    use quickcheck::{Arbitrary, Gen};
215
216    let mut g = Gen::new(32);
217    for _ in 0..128 {
218      let addr = Eui64Addr::arbitrary(&mut g);
219      let parsed =
220        Eui64Addr::try_from(addr.to_string().as_str()).expect("to_string() output must parse back");
221      assert_eq!(addr, parsed);
222    }
223  }
224
225  #[cfg(feature = "quickcheck")]
226  #[test]
227  fn quickcheck_shrink_terminates_and_preserves_length() {
228    use quickcheck::Arbitrary;
229
230    let addr = Eui64Addr::from_raw([0xFF; EUI64_ADDRESS_SIZE]);
231    let shrinks: Vec<_> = addr.shrink().take(4096).collect();
232    assert!(!shrinks.is_empty(), "non-zero address should yield shrinks");
233    for s in &shrinks {
234      assert_eq!(s.octets().len(), EUI64_ADDRESS_SIZE);
235    }
236  }
237
238  #[cfg(feature = "quickcheck")]
239  #[test]
240  fn quickcheck_shrink_zero_is_empty() {
241    use quickcheck::Arbitrary;
242
243    let zero = Eui64Addr::from_raw([0; EUI64_ADDRESS_SIZE]);
244    let shrinks: Vec<_> = zero.shrink().collect();
245    assert!(
246      shrinks.is_empty(),
247      "zero address should yield no shrinks, got {:?}",
248      shrinks
249    );
250  }
251
252  #[cfg(feature = "quickcheck")]
253  #[test]
254  fn quickcheck_roundtrip_property() {
255    fn prop(addr: Eui64Addr) -> bool {
256      Eui64Addr::try_from(addr.to_string().as_str())
257        .map(|p| p == addr)
258        .unwrap_or(false)
259    }
260    quickcheck::quickcheck(prop as fn(Eui64Addr) -> bool);
261  }
262}