ieee80211/
ssid.rs

1// Copyright 2021 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 arbitrary::Arbitrary;
6use fidl_fuchsia_wlan_ieee80211::{self as fidl_ieee80211, MAX_SSID_BYTE_LEN};
7use std::borrow::Cow;
8use std::ops::{Deref, Index};
9use std::slice::SliceIndex;
10use std::{fmt, str};
11use thiserror::Error;
12
13#[derive(Debug, Error)]
14#[cfg_attr(test, derive(PartialEq, Eq))]
15#[non_exhaustive]
16pub enum SsidError {
17    #[error("Invalid SSID length: {0} bytes (maximum is {MAX_SSID_BYTE_LEN})")]
18    Size(usize),
19}
20
21/// A newtype wrapping a boxed slice with bytes representing an SSID
22/// element as defined by IEEE 802.11-2016 9.4.2.2.
23///
24/// Bytes representing an SSID read from a FIDL struct or persistent
25/// storage should be immediately wrapped by this type to ensure proper
26/// type-checking for their use as an SSID elsewhere in WLAN components.
27///
28/// The wrapped bytes are kept private to enforce the use of defined
29/// methods to create a value of type [`Ssid`]. This ensures an [`Ssid`]
30/// never contains more than the limit of 32 bytes.
31///
32/// [`Ssid`]: crate::ssid::Ssid
33///
34/// # Examples
35///
36/// Usage:
37/// ```
38/// let ssid: Ssid = Ssid::try_from("foo").unwrap();
39/// assert_eq!(ssid, Ssid([0x66, 0x6F, 0x6F].into()));
40///
41/// let ssid: Ssid = Ssid::try_from([1, 2, 3, 4, 5]).unwrap();
42/// assert_eq!(ssid, Ssid([0x01, 0x02, 0x03, 0x04, 0x05].into()));
43///
44/// let ssid: Ssid = Ssid::try_from(vec![1, 2, 3, 4, 5]).unwrap();
45/// assert_eq!(ssid, Ssid([0x01, 0x02, 0x03, 0x04, 0x05].into()));
46///
47/// assert_eq!(ssid, [0x01, 0x02, 0x03, 0x04, 0x05]);
48/// assert_eq!(ssid, vec![0x01, 0x02, 0x03, 0x04, 0x05]);
49/// ```
50#[derive(Arbitrary)] // Derive Arbitrary for fuzzer
51#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
52pub struct Ssid(Box<[u8]>);
53
54impl AsRef<[u8]> for Ssid {
55    fn as_ref(&self) -> &[u8] {
56        &self.0[..]
57    }
58}
59
60impl Deref for Ssid {
61    type Target = [u8];
62
63    fn deref(&self) -> &[u8] {
64        &self.0
65    }
66}
67
68impl fmt::Display for Ssid {
69    /// Return an SSID formatted as <ssid-BYTES> where BYTES are the bytes of the
70    /// SSID encoded as uppercase hexadecimal characters.
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "<ssid-{}>", hex::encode(self.0.clone()))
73    }
74}
75
76impl From<Ssid> for String {
77    fn from(ssid: Ssid) -> String {
78        ssid.to_string()
79    }
80}
81
82impl From<Ssid> for Vec<u8> {
83    fn from(ssid: Ssid) -> Vec<u8> {
84        ssid.0.into_vec()
85    }
86}
87
88impl<Idx> Index<Idx> for Ssid
89where
90    Idx: SliceIndex<[u8]>,
91{
92    type Output = Idx::Output;
93
94    fn index(&self, index: Idx) -> &Self::Output {
95        &self.0[index]
96    }
97}
98
99impl PartialEq<Ssid> for Vec<u8> {
100    fn eq(&self, other: &Ssid) -> bool {
101        Ssid::deref(other)[..] == self[..]
102    }
103}
104
105impl PartialEq<Vec<u8>> for Ssid {
106    fn eq(&self, other: &Vec<u8>) -> bool {
107        Ssid::deref(self)[..] == other[..]
108    }
109}
110
111impl<const N: usize> PartialEq<[u8; N]> for Ssid {
112    fn eq(&self, other: &[u8; N]) -> bool {
113        Ssid::deref(self)[..] == other[..]
114    }
115}
116
117impl<const N: usize> PartialEq<Ssid> for [u8; N] {
118    fn eq(&self, other: &Ssid) -> bool {
119        Ssid::deref(other)[..] == self[..]
120    }
121}
122
123impl PartialEq<[u8]> for Ssid {
124    fn eq(&self, other: &[u8]) -> bool {
125        Ssid::deref(self)[..] == other[..]
126    }
127}
128
129impl PartialEq<Ssid> for [u8] {
130    fn eq(&self, other: &Ssid) -> bool {
131        Ssid::deref(other)[..] == self[..]
132    }
133}
134
135impl TryFrom<Box<[u8]>> for Ssid {
136    type Error = SsidError;
137
138    fn try_from(bytes: Box<[u8]>) -> Result<Self, Self::Error> {
139        Ssid::check(&bytes)?;
140        Ok(Ssid(bytes))
141    }
142}
143
144impl<const N: usize> TryFrom<[u8; N]> for Ssid {
145    type Error = SsidError;
146
147    fn try_from(bytes: [u8; N]) -> Result<Self, Self::Error> {
148        Ssid::check(&bytes)?;
149        Ok(Ssid(bytes.into()))
150    }
151}
152
153impl TryFrom<Box<str>> for Ssid {
154    type Error = SsidError;
155
156    fn try_from(s: Box<str>) -> Result<Self, Self::Error> {
157        s.into_boxed_bytes().try_into()
158    }
159}
160
161impl TryFrom<String> for Ssid {
162    type Error = SsidError;
163
164    fn try_from(s: String) -> Result<Self, Self::Error> {
165        s.into_boxed_str().try_into()
166    }
167}
168
169impl TryFrom<Vec<u8>> for Ssid {
170    type Error = SsidError;
171
172    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
173        bytes.into_boxed_slice().try_into()
174    }
175}
176
177impl TryFrom<&str> for Ssid {
178    type Error = SsidError;
179
180    fn try_from(s: &str) -> Result<Self, Self::Error> {
181        Ssid::check(s.as_bytes())?;
182        Ok(Ssid(String::from(s).into_boxed_str().into_boxed_bytes()))
183    }
184}
185
186impl TryFrom<&[u8]> for Ssid {
187    type Error = SsidError;
188
189    fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
190        Ssid::check(s)?;
191        Ok(Ssid(s.to_vec().into_boxed_slice()))
192    }
193}
194
195impl Ssid {
196    /// Returns an `Ssid` for `ssid_bytes` from a source that already enforces the size of
197    /// bytes does not exceed fidl_ieee80211::MAX_SSID_BYTE_LEN. This function should only
198    /// be used when the caller is certain the limit is not exceeded since the check is
199    /// intentionally skipped.
200    ///
201    /// For example, fuchsia.wlan.* FIDL messages use the fuchsia.wlan.ieee80211/Ssid alias
202    /// for all messages that accept or return an SSID. This alias imposes the required
203    /// maximum limit on SSID in the FIDL message itself and is therefore impossible to
204    /// exceed.
205    pub fn from_bytes_unchecked(ssid_bytes: Vec<u8>) -> Ssid {
206        Ssid(ssid_bytes.into_boxed_slice())
207    }
208
209    /// Return an SSID formatted as a UTF-8 string, or <ssid-BYTES> if a UTF-8 error
210    /// is encountered.
211    pub fn to_string_not_redactable(&self) -> Cow<'_, str> {
212        str::from_utf8(self.as_ref()).map(From::from).unwrap_or_else(|_| self.to_string().into())
213    }
214
215    pub fn empty() -> Ssid {
216        Ssid([].into())
217    }
218
219    pub fn len(&self) -> usize {
220        self.0.len()
221    }
222
223    pub fn to_vec(&self) -> Vec<u8> {
224        self.0.clone().into()
225    }
226
227    fn check(bytes: &[u8]) -> Result<(), SsidError> {
228        if bytes.len() > (fidl_ieee80211::MAX_SSID_BYTE_LEN as usize) {
229            return Err(SsidError::Size(bytes.len()));
230        }
231        Ok(())
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use std::sync::LazyLock;
239
240    static SSID_12345: LazyLock<Ssid> =
241        LazyLock::new(|| Ssid::try_from([0x01, 0x02, 0x03, 0x04, 0x05]).unwrap());
242    static SSID_FOO: LazyLock<Ssid> = LazyLock::new(|| Ssid::try_from([0x66, 0x6F, 0x6F]).unwrap());
243
244    #[test]
245    fn ssid_check() {
246        assert_eq!(Ok(()), Ssid::check(&[0x2; 0]));
247        assert_eq!(Ok(()), Ssid::check(&[0x2; 20]));
248        assert_eq!(Ok(()), Ssid::check(&[0x2; 32]));
249        assert_eq!(Err(SsidError::Size(33)), Ssid::check(&[0x2; 33]));
250    }
251
252    #[test]
253    fn ssid_as_ref() {
254        let array_12345: [u8; 5] = [0x01, 0x02, 0x03, 0x04, 0x05];
255        assert_eq!((*SSID_12345).as_ref(), array_12345.as_ref());
256    }
257
258    #[test]
259    fn ssid_deref() {
260        let array_12345: [u8; 5] = [0x01, 0x02, 0x03, 0x04, 0x05];
261        assert_eq!(**SSID_12345, array_12345);
262    }
263
264    #[test]
265    fn ssid_to_string() {
266        assert_eq!(Ssid::empty().to_string(), "<ssid->");
267        assert_eq!(SSID_FOO.to_string(), "<ssid-666f6f>");
268        assert_eq!(SSID_12345.to_string(), "<ssid-0102030405>");
269    }
270
271    #[test]
272    fn format_ssid() {
273        assert_eq!(format!("{}", *SSID_12345), "<ssid-0102030405>");
274    }
275
276    #[test]
277    fn ssid_into_vec() {
278        assert_eq!(
279            <Ssid as Into<Vec<u8>>>::into(SSID_12345.clone()),
280            vec![0x01, 0x02, 0x03, 0x04, 0x05]
281        );
282    }
283
284    #[test]
285    fn ssid_try_from_boxed_slice_ok() {
286        let ssid: Ssid =
287            <Ssid as TryFrom<Box<[u8]>>>::try_from(Box::from([0x01, 0x02, 0x03, 0x04, 0x05]))
288                .expect("Failed to convert Box<[u8]> to Ssid");
289        assert_eq!(ssid, *SSID_12345);
290    }
291
292    #[test]
293    fn ssid_try_from_array_ok() {
294        let array = [0x01, 0x02, 0x03, 0x04, 0x05];
295        let ssid: Ssid =
296            <Ssid as TryFrom<[u8; 5]>>::try_from(array).expect("Failed to convert [u8; 5] to Ssid");
297        assert_eq!(ssid, *SSID_12345);
298    }
299
300    #[test]
301    fn ssid_try_from_boxed_str_ok() {
302        let ssid: Ssid =
303            <Ssid as TryFrom<Box<str>>>::try_from(String::from("foo").into_boxed_str())
304                .expect("Failed to convert Box<str> to Ssid");
305        assert_eq!(ssid, *SSID_FOO);
306    }
307
308    #[test]
309    fn ssid_try_from_string_ok() {
310        let ssid: Ssid = <Ssid as TryFrom<String>>::try_from(String::from("foo"))
311            .expect("Failed to convert String to Ssid");
312        assert_eq!(ssid, *SSID_FOO);
313    }
314
315    #[test]
316    fn ssid_try_from_vec_ok() {
317        let ssid: Ssid = <Ssid as TryFrom<Vec<u8>>>::try_from(vec![0x01, 0x02, 0x03, 0x04, 0x05])
318            .expect("Failed to convert Vec<u8> to Ssid");
319        assert_eq!(ssid, *SSID_12345);
320    }
321
322    #[test]
323    fn ssid_try_from_str_ok() {
324        let ssid: Ssid =
325            <Ssid as TryFrom<&str>>::try_from("foo").expect("Failed to convert &str to Ssid");
326        assert_eq!(ssid, *SSID_FOO);
327    }
328
329    #[test]
330    fn ssid_try_from_slice_ok() {
331        let ssid: Ssid = <Ssid as TryFrom<&[u8]>>::try_from(&[0x01, 0x02, 0x03, 0x04, 0x05])
332            .expect("Failed to convert &[u8] to Ssid");
333        assert_eq!(ssid, *SSID_12345);
334    }
335
336    #[test]
337    fn ssid_try_from_array_err() {
338        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<[u8; 32]>>::try_from([0x03; 32]);
339        assert!(matches!(ssid, Ok(_)));
340        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<[u8; 50]>>::try_from([0x03; 50]);
341        assert!(matches!(ssid, Err(SsidError::Size(50))));
342    }
343
344    #[test]
345    fn ssid_try_from_string_err() {
346        let ssid: Result<Ssid, SsidError> =
347            <Ssid as TryFrom<String>>::try_from(String::from("12345678901234567890123456789012"));
348        assert!(matches!(ssid, Ok(_)));
349        let ssid: Result<Ssid, SsidError> =
350            <Ssid as TryFrom<String>>::try_from(String::from("123456789012345678901234567890123"));
351        assert!(matches!(ssid, Err(SsidError::Size(33))));
352    }
353
354    #[test]
355    fn ssid_try_from_vec_err() {
356        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<Vec<u8>>>::try_from(vec![0x07; 32]);
357        assert!(matches!(ssid, Ok(_)));
358        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<Vec<u8>>>::try_from(vec![0x07; 100]);
359        assert!(matches!(ssid, Err(SsidError::Size(100))));
360    }
361
362    #[test]
363    fn ssid_try_from_str_err() {
364        let ssid: Result<Ssid, SsidError> =
365            <Ssid as TryFrom<&str>>::try_from("12345678901234567890123456789012");
366        assert!(matches!(ssid, Ok(_)));
367        let ssid: Result<Ssid, SsidError> =
368            <Ssid as TryFrom<&str>>::try_from("123456789012345678901234567890123");
369        assert!(matches!(ssid, Err(SsidError::Size(33))));
370    }
371
372    #[test]
373    fn ssid_try_from_slice_err() {
374        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<&[u8]>>::try_from(&[0x01; 32]);
375        assert!(matches!(ssid, Ok(_)));
376        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<&[u8]>>::try_from(&[0x01; 33]);
377        assert!(matches!(ssid, Err(SsidError::Size(33))));
378    }
379
380    #[test]
381    fn ssid_index() {
382        assert_eq!(SSID_12345[0], 0x01);
383        assert_eq!(SSID_12345[1], 0x02);
384        assert_eq!(SSID_12345[2], 0x03);
385        assert_eq!(SSID_12345[3], 0x04);
386        assert_eq!(SSID_12345[4], 0x05);
387    }
388
389    #[test]
390    fn ssid_partial_eq_vec() {
391        assert_eq!(vec![], Ssid::empty());
392        assert_eq!(Ssid::empty(), vec![]);
393
394        assert_eq!(vec![1, 2, 3], Ssid::try_from([1, 2, 3]).unwrap());
395        assert_eq!(Ssid::try_from([1, 2, 3]).unwrap(), vec![1, 2, 3]);
396
397        assert_ne!(vec![1, 2], Ssid::try_from([1, 2, 3]).unwrap());
398        assert_ne!(Ssid::try_from([1, 2, 3]).unwrap(), vec![1, 2]);
399    }
400
401    #[test]
402    fn ssid_partial_eq_array() {
403        assert_eq!([], Ssid::empty());
404        assert_eq!(Ssid::empty(), []);
405
406        assert_eq!([1, 2, 3], Ssid::try_from([1, 2, 3]).unwrap());
407        assert_eq!(Ssid::try_from([1, 2, 3]).unwrap(), [1, 2, 3]);
408
409        assert_ne!([1, 2], Ssid::try_from([1, 2, 3]).unwrap());
410        assert_ne!(Ssid::try_from([1, 2, 3]).unwrap(), [1, 2]);
411    }
412
413    #[test]
414    fn ssid_partial_eq_slice() {
415        assert_eq!(&b""[..], &Ssid::empty());
416        assert_eq!(&Ssid::empty(), &b""[..]);
417
418        assert_eq!(&[1, 2, 3][..], &Ssid::try_from([1, 2, 3]).unwrap());
419        assert_eq!(&Ssid::try_from([1, 2, 3]).unwrap(), &[1, 2, 3][..]);
420
421        assert_ne!(&[1, 2][..], &Ssid::try_from([1, 2, 3]).unwrap());
422        assert_ne!(&Ssid::try_from([1, 2, 3]).unwrap(), &[1, 2][..]);
423    }
424
425    #[test]
426    fn ssid_to_string_not_redactable() {
427        assert_eq!(Ssid::empty().to_string_not_redactable(), "");
428
429        let sparkle_heart_ssid: Ssid = Ssid::try_from("💖").unwrap();
430        assert_eq!(sparkle_heart_ssid.to_string_not_redactable(), "💖");
431
432        let invalid_utf8_ssid: Ssid = Ssid::try_from([0x00, 0x9f, 0x92, 0x96]).unwrap();
433        assert_eq!(invalid_utf8_ssid.to_string_not_redactable(), "<ssid-009f9296>");
434    }
435
436    #[test]
437    fn ssid_empty() {
438        assert_eq!(Ssid::empty(), Ssid::try_from([]).unwrap());
439        assert_eq!(vec![], Ssid::empty());
440        assert_eq!(Ssid::empty().to_string(), "<ssid->");
441        assert_eq!(Ssid::empty().to_string_not_redactable(), "");
442    }
443
444    #[test]
445    fn ssid_len() {
446        assert_eq!(Ssid::empty().len(), 0);
447        assert_eq!(SSID_FOO.len(), 3);
448        assert_eq!(SSID_12345.len(), 5);
449    }
450
451    #[test]
452    fn ssid_to_vec() {
453        let ssid = SSID_12345.clone();
454        assert_eq!(ssid.to_vec(), vec![0x01, 0x02, 0x03, 0x04, 0x05]);
455        // ssid not dropped
456        assert_eq!(<Ssid as Into<Vec<u8>>>::into(ssid), vec![0x01, 0x02, 0x03, 0x04, 0x05]);
457    }
458}