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 lazy_static::lazy_static;
239
240    lazy_static! {
241        static ref SSID_12345: Ssid = Ssid::try_from([0x01, 0x02, 0x03, 0x04, 0x05]).unwrap();
242        static ref SSID_FOO: Ssid = Ssid::try_from([0x66, 0x6F, 0x6F]).unwrap();
243    }
244
245    #[test]
246    fn ssid_check() {
247        assert_eq!(Ok(()), Ssid::check(&[0x2; 0]));
248        assert_eq!(Ok(()), Ssid::check(&[0x2; 20]));
249        assert_eq!(Ok(()), Ssid::check(&[0x2; 32]));
250        assert_eq!(Err(SsidError::Size(33)), Ssid::check(&[0x2; 33]));
251    }
252
253    #[test]
254    fn ssid_as_ref() {
255        let array_12345: [u8; 5] = [0x01, 0x02, 0x03, 0x04, 0x05];
256        assert_eq!((*SSID_12345).as_ref(), array_12345.as_ref());
257    }
258
259    #[test]
260    fn ssid_deref() {
261        let array_12345: [u8; 5] = [0x01, 0x02, 0x03, 0x04, 0x05];
262        assert_eq!(**SSID_12345, array_12345);
263    }
264
265    #[test]
266    fn ssid_to_string() {
267        assert_eq!(Ssid::empty().to_string(), "<ssid->");
268        assert_eq!(SSID_FOO.to_string(), "<ssid-666f6f>");
269        assert_eq!(SSID_12345.to_string(), "<ssid-0102030405>");
270    }
271
272    #[test]
273    fn format_ssid() {
274        assert_eq!(format!("{}", *SSID_12345), "<ssid-0102030405>");
275    }
276
277    #[test]
278    fn ssid_into_vec() {
279        assert_eq!(
280            <Ssid as Into<Vec<u8>>>::into(SSID_12345.clone()),
281            vec![0x01, 0x02, 0x03, 0x04, 0x05]
282        );
283    }
284
285    #[test]
286    fn ssid_try_from_boxed_slice_ok() {
287        let ssid: Ssid =
288            <Ssid as TryFrom<Box<[u8]>>>::try_from(Box::from([0x01, 0x02, 0x03, 0x04, 0x05]))
289                .expect("Failed to convert Box<[u8]> to Ssid");
290        assert_eq!(ssid, *SSID_12345);
291    }
292
293    #[test]
294    fn ssid_try_from_array_ok() {
295        let array = [0x01, 0x02, 0x03, 0x04, 0x05];
296        let ssid: Ssid =
297            <Ssid as TryFrom<[u8; 5]>>::try_from(array).expect("Failed to convert [u8; 5] to Ssid");
298        assert_eq!(ssid, *SSID_12345);
299    }
300
301    #[test]
302    fn ssid_try_from_boxed_str_ok() {
303        let ssid: Ssid =
304            <Ssid as TryFrom<Box<str>>>::try_from(String::from("foo").into_boxed_str())
305                .expect("Failed to convert Box<str> to Ssid");
306        assert_eq!(ssid, *SSID_FOO);
307    }
308
309    #[test]
310    fn ssid_try_from_string_ok() {
311        let ssid: Ssid = <Ssid as TryFrom<String>>::try_from(String::from("foo"))
312            .expect("Failed to convert String to Ssid");
313        assert_eq!(ssid, *SSID_FOO);
314    }
315
316    #[test]
317    fn ssid_try_from_vec_ok() {
318        let ssid: Ssid = <Ssid as TryFrom<Vec<u8>>>::try_from(vec![0x01, 0x02, 0x03, 0x04, 0x05])
319            .expect("Failed to convert Vec<u8> to Ssid");
320        assert_eq!(ssid, *SSID_12345);
321    }
322
323    #[test]
324    fn ssid_try_from_str_ok() {
325        let ssid: Ssid =
326            <Ssid as TryFrom<&str>>::try_from("foo").expect("Failed to convert &str to Ssid");
327        assert_eq!(ssid, *SSID_FOO);
328    }
329
330    #[test]
331    fn ssid_try_from_slice_ok() {
332        let ssid: Ssid = <Ssid as TryFrom<&[u8]>>::try_from(&[0x01, 0x02, 0x03, 0x04, 0x05])
333            .expect("Failed to convert &[u8] to Ssid");
334        assert_eq!(ssid, *SSID_12345);
335    }
336
337    #[test]
338    fn ssid_try_from_array_err() {
339        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<[u8; 32]>>::try_from([0x03; 32]);
340        assert!(matches!(ssid, Ok(_)));
341        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<[u8; 50]>>::try_from([0x03; 50]);
342        assert!(matches!(ssid, Err(SsidError::Size(50))));
343    }
344
345    #[test]
346    fn ssid_try_from_string_err() {
347        let ssid: Result<Ssid, SsidError> =
348            <Ssid as TryFrom<String>>::try_from(String::from("12345678901234567890123456789012"));
349        assert!(matches!(ssid, Ok(_)));
350        let ssid: Result<Ssid, SsidError> =
351            <Ssid as TryFrom<String>>::try_from(String::from("123456789012345678901234567890123"));
352        assert!(matches!(ssid, Err(SsidError::Size(33))));
353    }
354
355    #[test]
356    fn ssid_try_from_vec_err() {
357        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<Vec<u8>>>::try_from(vec![0x07; 32]);
358        assert!(matches!(ssid, Ok(_)));
359        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<Vec<u8>>>::try_from(vec![0x07; 100]);
360        assert!(matches!(ssid, Err(SsidError::Size(100))));
361    }
362
363    #[test]
364    fn ssid_try_from_str_err() {
365        let ssid: Result<Ssid, SsidError> =
366            <Ssid as TryFrom<&str>>::try_from("12345678901234567890123456789012");
367        assert!(matches!(ssid, Ok(_)));
368        let ssid: Result<Ssid, SsidError> =
369            <Ssid as TryFrom<&str>>::try_from("123456789012345678901234567890123");
370        assert!(matches!(ssid, Err(SsidError::Size(33))));
371    }
372
373    #[test]
374    fn ssid_try_from_slice_err() {
375        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<&[u8]>>::try_from(&[0x01; 32]);
376        assert!(matches!(ssid, Ok(_)));
377        let ssid: Result<Ssid, SsidError> = <Ssid as TryFrom<&[u8]>>::try_from(&[0x01; 33]);
378        assert!(matches!(ssid, Err(SsidError::Size(33))));
379    }
380
381    #[test]
382    fn ssid_index() {
383        assert_eq!(SSID_12345[0], 0x01);
384        assert_eq!(SSID_12345[1], 0x02);
385        assert_eq!(SSID_12345[2], 0x03);
386        assert_eq!(SSID_12345[3], 0x04);
387        assert_eq!(SSID_12345[4], 0x05);
388    }
389
390    #[test]
391    fn ssid_partial_eq_vec() {
392        assert_eq!(vec![], Ssid::empty());
393        assert_eq!(Ssid::empty(), vec![]);
394
395        assert_eq!(vec![1, 2, 3], Ssid::try_from([1, 2, 3]).unwrap());
396        assert_eq!(Ssid::try_from([1, 2, 3]).unwrap(), vec![1, 2, 3]);
397
398        assert_ne!(vec![1, 2], Ssid::try_from([1, 2, 3]).unwrap());
399        assert_ne!(Ssid::try_from([1, 2, 3]).unwrap(), vec![1, 2]);
400    }
401
402    #[test]
403    fn ssid_partial_eq_array() {
404        assert_eq!([], Ssid::empty());
405        assert_eq!(Ssid::empty(), []);
406
407        assert_eq!([1, 2, 3], Ssid::try_from([1, 2, 3]).unwrap());
408        assert_eq!(Ssid::try_from([1, 2, 3]).unwrap(), [1, 2, 3]);
409
410        assert_ne!([1, 2], Ssid::try_from([1, 2, 3]).unwrap());
411        assert_ne!(Ssid::try_from([1, 2, 3]).unwrap(), [1, 2]);
412    }
413
414    #[test]
415    fn ssid_partial_eq_slice() {
416        assert_eq!(&b""[..], &Ssid::empty());
417        assert_eq!(&Ssid::empty(), &b""[..]);
418
419        assert_eq!(&[1, 2, 3][..], &Ssid::try_from([1, 2, 3]).unwrap());
420        assert_eq!(&Ssid::try_from([1, 2, 3]).unwrap(), &[1, 2, 3][..]);
421
422        assert_ne!(&[1, 2][..], &Ssid::try_from([1, 2, 3]).unwrap());
423        assert_ne!(&Ssid::try_from([1, 2, 3]).unwrap(), &[1, 2][..]);
424    }
425
426    #[test]
427    fn ssid_to_string_not_redactable() {
428        assert_eq!(Ssid::empty().to_string_not_redactable(), "");
429
430        let sparkle_heart_ssid: Ssid = Ssid::try_from("💖").unwrap();
431        assert_eq!(sparkle_heart_ssid.to_string_not_redactable(), "💖");
432
433        let invalid_utf8_ssid: Ssid = Ssid::try_from([0x00, 0x9f, 0x92, 0x96]).unwrap();
434        assert_eq!(invalid_utf8_ssid.to_string_not_redactable(), "<ssid-009f9296>");
435    }
436
437    #[test]
438    fn ssid_empty() {
439        assert_eq!(Ssid::empty(), Ssid::try_from([]).unwrap());
440        assert_eq!(vec![], Ssid::empty());
441        assert_eq!(Ssid::empty().to_string(), "<ssid->");
442        assert_eq!(Ssid::empty().to_string_not_redactable(), "");
443    }
444
445    #[test]
446    fn ssid_len() {
447        assert_eq!(Ssid::empty().len(), 0);
448        assert_eq!(SSID_FOO.len(), 3);
449        assert_eq!(SSID_12345.len(), 5);
450    }
451
452    #[test]
453    fn ssid_to_vec() {
454        let ssid = SSID_12345.clone();
455        assert_eq!(ssid.to_vec(), vec![0x01, 0x02, 0x03, 0x04, 0x05]);
456        // ssid not dropped
457        assert_eq!(<Ssid as Into<Vec<u8>>>::into(ssid), vec![0x01, 0x02, 0x03, 0x04, 0x05]);
458    }
459}