1pub const SSID_MAX_LEN: usize = 32;
26pub const MAC_LEN: usize = 6;
28pub const PMK_LEN: usize = 32;
30pub const PASSPHRASE_MIN_LEN: usize = 8;
32pub const PASSPHRASE_MAX_LEN: usize = 63;
34pub const SAE_PASSWORD_MAX_LEN: usize = 128;
37
38pub const FLAG_HIDDEN: u8 = 1 << 0;
41pub const FLAG_RAW_KEY: u8 = 1 << 1;
44
45const FLAGS_RESERVED: u8 = !(FLAG_HIDDEN | FLAG_RAW_KEY);
46
47pub const NETWORK_ENTRY_MAX_LEN: usize = 3 + SSID_MAX_LEN + SAE_PASSWORD_MAX_LEN;
50pub const SCAN_RESULT_MAX_LEN: usize = 11 + SSID_MAX_LEN;
52pub const LINK_MAX_LEN: usize = 10;
54pub const AP_CONFIG_MAX_LEN: usize = 11 + SSID_MAX_LEN + SAE_PASSWORD_MAX_LEN;
56pub const AP_CLIENT_LEN: usize = 11;
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum WifiError {
62 Truncated,
65 Malformed,
68 NotWellFormed,
73 BufferTooSmall,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum SecurityMode {
81 Open = 0,
83 Owe = 1,
87 Wpa2 = 2,
90 Wpa3 = 3,
92 Wpa = 4,
95 Wep = 5,
98 Wpa2Ent = 6,
100 Wpa3Ent = 7,
102 Wpa3Ent192 = 8,
104}
105
106impl SecurityMode {
107 pub const fn permits(self, negotiated: Self, raw_key: bool) -> bool {
110 match (self, negotiated) {
111 (Self::Open, Self::Open | Self::Owe) | (Self::Owe, Self::Owe) => !raw_key,
112 (Self::Wpa, Self::Wpa | Self::Wpa2) | (Self::Wpa2, Self::Wpa2) => true,
113 (Self::Wpa | Self::Wpa2 | Self::Wpa3, Self::Wpa3) => !raw_key,
114 _ => false,
115 }
116 }
117
118 pub fn strongest_compatible(
121 self,
122 advertised: u16,
123 supported: u16,
124 raw_key: bool,
125 ) -> Option<Self> {
126 [Self::Wpa3, Self::Wpa2, Self::Wpa, Self::Owe, Self::Open]
127 .into_iter()
128 .find(|mode| advertised & supported & mode.bit() != 0 && self.permits(*mode, raw_key))
129 }
130
131 pub const fn code(self) -> u8 {
133 self as u8
134 }
135
136 pub const fn from_code(code: u8) -> Option<Self> {
140 match code {
141 0 => Some(Self::Open),
142 1 => Some(Self::Owe),
143 2 => Some(Self::Wpa2),
144 3 => Some(Self::Wpa3),
145 4 => Some(Self::Wpa),
146 5 => Some(Self::Wep),
147 6 => Some(Self::Wpa2Ent),
148 7 => Some(Self::Wpa3Ent),
149 8 => Some(Self::Wpa3Ent192),
150 _ => None,
151 }
152 }
153
154 pub const fn bit(self) -> u16 {
156 1 << (self as u16)
157 }
158
159 pub const fn has_credential_form(self) -> bool {
163 matches!(
164 self,
165 Self::Open | Self::Owe | Self::Wpa2 | Self::Wpa3 | Self::Wpa
166 )
167 }
168
169 pub const fn derives_key_from_passphrase(self) -> bool {
175 matches!(self, Self::Wpa2 | Self::Wpa)
176 }
177
178 pub const fn may_be_offered(self) -> bool {
180 matches!(self, Self::Open | Self::Owe | Self::Wpa2 | Self::Wpa3)
181 }
182
183 pub const fn passphrase_len_bounds(self) -> Option<(usize, usize)> {
186 match self {
187 Self::Wpa2 | Self::Wpa => Some((PASSPHRASE_MIN_LEN, PASSPHRASE_MAX_LEN)),
188 Self::Wpa3 => Some((1, SAE_PASSWORD_MAX_LEN)),
189 _ => None,
190 }
191 }
192}
193
194const fn le16(bytes: &[u8], at: usize) -> Option<u16> {
196 if at + 2 > bytes.len() {
197 return None;
198 }
199 Some((bytes[at] as u16) | ((bytes[at + 1] as u16) << 8))
200}
201
202fn passphrase_is_expressible(bytes: &[u8]) -> bool {
206 !bytes.contains(&0) && core::str::from_utf8(bytes).is_ok()
207}
208
209fn credential_is_well_formed(security: SecurityMode, raw_key: bool, credential: &[u8]) -> bool {
211 if !security.has_credential_form() {
212 return false;
213 }
214 if raw_key {
215 return security.derives_key_from_passphrase() && credential.len() == PMK_LEN;
216 }
217 match security.passphrase_len_bounds() {
218 None => credential.is_empty(),
220 Some((min, max)) => {
221 credential.len() >= min
222 && credential.len() <= max
223 && passphrase_is_expressible(credential)
224 }
225 }
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
234pub struct NetworkEntry<'a> {
235 pub hidden: bool,
237 pub raw_key: bool,
240 pub security: SecurityMode,
241 pub ssid: &'a [u8],
245 pub credential: &'a [u8],
247}
248
249impl<'a> NetworkEntry<'a> {
250 pub const fn flags(&self) -> u8 {
252 let mut flags = 0;
253 if self.hidden {
254 flags |= FLAG_HIDDEN;
255 }
256 if self.raw_key {
257 flags |= FLAG_RAW_KEY;
258 }
259 flags
260 }
261
262 pub fn validate(&self) -> Result<(), WifiError> {
271 if self.ssid.is_empty() || self.ssid.len() > SSID_MAX_LEN {
272 return Err(WifiError::NotWellFormed);
273 }
274 if !credential_is_well_formed(self.security, self.raw_key, self.credential) {
275 return Err(WifiError::NotWellFormed);
276 }
277 Ok(())
278 }
279
280 fn encode_inner(&self, out: &mut [u8], with_credential: bool) -> Result<usize, WifiError> {
281 let credential: &[u8] = if with_credential {
282 self.credential
283 } else {
284 &[]
285 };
286 let len = 3 + self.ssid.len() + credential.len();
287 if self.ssid.len() > SSID_MAX_LEN {
288 return Err(WifiError::NotWellFormed);
289 }
290 if out.len() < len {
291 return Err(WifiError::BufferTooSmall);
292 }
293 out[0] = self.flags();
294 out[1] = self.security.code();
295 out[2] = self.ssid.len() as u8;
296 out[3..3 + self.ssid.len()].copy_from_slice(self.ssid);
297 out[3 + self.ssid.len()..len].copy_from_slice(credential);
298 Ok(len)
299 }
300
301 pub fn encode(&self, out: &mut [u8]) -> Result<usize, WifiError> {
303 self.encode_inner(out, true)
304 }
305
306 pub fn encode_reported(&self, out: &mut [u8]) -> Result<usize, WifiError> {
309 self.encode_inner(out, false)
310 }
311
312 pub fn decode(bytes: &'a [u8]) -> Result<Self, WifiError> {
315 if bytes.len() < 3 {
316 return Err(WifiError::Truncated);
317 }
318 let flags = bytes[0];
319 if flags & FLAGS_RESERVED != 0 {
320 return Err(WifiError::Malformed);
321 }
322 let security = SecurityMode::from_code(bytes[1]).ok_or(WifiError::Malformed)?;
323 let ssid_len = bytes[2] as usize;
324 if ssid_len > SSID_MAX_LEN {
325 return Err(WifiError::Malformed);
326 }
327 if bytes.len() < 3 + ssid_len {
328 return Err(WifiError::Truncated);
329 }
330 Ok(Self {
331 hidden: flags & FLAG_HIDDEN != 0,
332 raw_key: flags & FLAG_RAW_KEY != 0,
333 security,
334 ssid: &bytes[3..3 + ssid_len],
335 credential: &bytes[3 + ssid_len..],
336 })
337 }
338}
339
340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343pub struct ScanResult<'a> {
344 pub modes: u16,
349 pub frequency_mhz: u16,
351 pub rssi_dbm: i8,
352 pub bssid: [u8; MAC_LEN],
354 pub ssid: &'a [u8],
356}
357
358impl<'a> ScanResult<'a> {
359 pub const fn modes_known(&self) -> bool {
361 self.modes != 0
362 }
363
364 pub const fn offers(&self, mode: SecurityMode) -> bool {
366 self.modes & mode.bit() != 0
367 }
368
369 pub fn encode(&self, out: &mut [u8]) -> Result<usize, WifiError> {
370 if self.ssid.len() > SSID_MAX_LEN {
371 return Err(WifiError::NotWellFormed);
372 }
373 let len = 11 + self.ssid.len();
374 if out.len() < len {
375 return Err(WifiError::BufferTooSmall);
376 }
377 out[0..2].copy_from_slice(&self.modes.to_le_bytes());
378 out[2..4].copy_from_slice(&self.frequency_mhz.to_le_bytes());
379 out[4] = self.rssi_dbm as u8;
380 out[5..11].copy_from_slice(&self.bssid);
381 out[11..len].copy_from_slice(self.ssid);
382 Ok(len)
383 }
384
385 pub fn decode(bytes: &'a [u8]) -> Result<Self, WifiError> {
386 if bytes.len() < 11 {
387 return Err(WifiError::Truncated);
388 }
389 let ssid = &bytes[11..];
390 if ssid.len() > SSID_MAX_LEN {
391 return Err(WifiError::Malformed);
392 }
393 let mut bssid = [0u8; MAC_LEN];
394 bssid.copy_from_slice(&bytes[5..11]);
395 Ok(Self {
396 modes: le16(bytes, 0).ok_or(WifiError::Truncated)?,
397 frequency_mhz: le16(bytes, 2).ok_or(WifiError::Truncated)?,
398 rssi_dbm: bytes[4] as i8,
399 bssid,
400 ssid,
401 })
402 }
403}
404
405#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
407pub enum LinkState {
408 #[default]
411 Down = 0,
412 Connecting = 1,
415 Up = 2,
417}
418
419impl LinkState {
420 pub const fn code(self) -> u8 {
421 self as u8
422 }
423
424 pub const fn from_code(code: u8) -> Option<Self> {
425 match code {
426 0 => Some(Self::Down),
427 1 => Some(Self::Connecting),
428 2 => Some(Self::Up),
429 _ => None,
430 }
431 }
432}
433
434#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
436pub enum LinkReason {
437 #[default]
440 None = 0,
441 NotFound = 1,
443 Auth = 2,
445 Rejected = 3,
448 Lost = 4,
450 Other = 5,
452}
453
454impl LinkReason {
455 pub const fn code(self) -> u8 {
456 self as u8
457 }
458
459 pub const fn from_code(code: u8) -> Option<Self> {
460 match code {
461 0 => Some(Self::None),
462 1 => Some(Self::NotFound),
463 2 => Some(Self::Auth),
464 3 => Some(Self::Rejected),
465 4 => Some(Self::Lost),
466 5 => Some(Self::Other),
467 _ => None,
468 }
469 }
470}
471
472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
474pub struct Association {
475 pub bssid: [u8; MAC_LEN],
476 pub frequency_mhz: u16,
478}
479
480#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
482pub struct Link {
483 pub state: LinkState,
484 pub reason: LinkReason,
485 pub association: Option<Association>,
487}
488
489impl Link {
490 pub fn encode(&self, out: &mut [u8]) -> Result<usize, WifiError> {
491 let len = if self.association.is_some() { 10 } else { 2 };
492 if out.len() < len {
493 return Err(WifiError::BufferTooSmall);
494 }
495 out[0] = self.state.code();
496 out[1] = self.reason.code();
497 if let Some(association) = self.association {
498 out[2..8].copy_from_slice(&association.bssid);
499 out[8..10].copy_from_slice(&association.frequency_mhz.to_le_bytes());
500 }
501 Ok(len)
502 }
503
504 pub fn decode(bytes: &[u8]) -> Result<Self, WifiError> {
505 if bytes.len() < 2 {
506 return Err(WifiError::Truncated);
507 }
508 let state = LinkState::from_code(bytes[0]).ok_or(WifiError::Malformed)?;
509 let reason = LinkReason::from_code(bytes[1]).ok_or(WifiError::Malformed)?;
510 let association = if matches!(state, LinkState::Up) {
511 if bytes.len() < 10 {
512 return Err(WifiError::Truncated);
513 }
514 let mut bssid = [0u8; MAC_LEN];
515 bssid.copy_from_slice(&bytes[2..8]);
516 Some(Association {
517 bssid,
518 frequency_mhz: le16(bytes, 8).ok_or(WifiError::Truncated)?,
519 })
520 } else {
521 None
522 };
523 Ok(Self {
524 state,
525 reason,
526 association,
527 })
528 }
529}
530
531#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
533pub enum ApState {
534 #[default]
536 Down = 0,
537 Up = 1,
539}
540
541impl ApState {
542 pub const fn code(self) -> u8 {
543 self as u8
544 }
545
546 pub const fn from_code(code: u8) -> Option<Self> {
547 match code {
548 0 => Some(Self::Down),
549 1 => Some(Self::Up),
550 _ => None,
551 }
552 }
553}
554
555#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
557pub struct ApStatus {
558 pub state: ApState,
559 pub frequency_mhz: Option<u16>,
563}
564
565impl ApStatus {
566 pub fn encode(&self, out: &mut [u8]) -> Result<usize, WifiError> {
567 let len = if self.frequency_mhz.is_some() { 3 } else { 1 };
568 if out.len() < len {
569 return Err(WifiError::BufferTooSmall);
570 }
571 out[0] = self.state.code();
572 if let Some(frequency) = self.frequency_mhz {
573 out[1..3].copy_from_slice(&frequency.to_le_bytes());
574 }
575 Ok(len)
576 }
577
578 pub fn decode(bytes: &[u8]) -> Result<Self, WifiError> {
579 let state = ApState::from_code(*bytes.first().ok_or(WifiError::Truncated)?)
580 .ok_or(WifiError::Malformed)?;
581 let frequency_mhz = if matches!(state, ApState::Up) {
582 Some(le16(bytes, 1).ok_or(WifiError::Truncated)?)
583 } else {
584 None
585 };
586 Ok(Self {
587 state,
588 frequency_mhz,
589 })
590 }
591}
592
593#[derive(Clone, Copy, Debug, PartialEq, Eq)]
598pub struct ApConfig<'a> {
599 pub hidden: bool,
602 pub security: SecurityMode,
603 pub max_clients: u8,
607 pub frequency_mhz: u16,
610 pub address: [u8; 4],
613 pub prefix: u8,
616 pub ssid: &'a [u8],
617 pub credential: &'a [u8],
618}
619
620impl<'a> ApConfig<'a> {
621 pub const DEFAULT_ADDRESS: [u8; 4] = [192, 168, 4, 1];
624 pub const DEFAULT_PREFIX: u8 = 24;
626
627 pub const fn flags(&self) -> u8 {
628 if self.hidden { FLAG_HIDDEN } else { 0 }
629 }
630
631 pub fn uses_default_subnet(&self) -> bool {
633 self.address == [0; 4] && self.prefix == 0
634 }
635
636 pub fn validate(&self) -> Result<(), WifiError> {
644 if self.ssid.is_empty() || self.ssid.len() > SSID_MAX_LEN {
645 return Err(WifiError::NotWellFormed);
646 }
647 if !self.security.may_be_offered() {
648 return Err(WifiError::NotWellFormed);
649 }
650 if !credential_is_well_formed(self.security, false, self.credential) {
653 return Err(WifiError::NotWellFormed);
654 }
655 if !self.uses_default_subnet() {
656 if !(8..=30).contains(&self.prefix) {
657 return Err(WifiError::NotWellFormed);
658 }
659 if !crate::ip::ipv4_is_usable_host(&self.address) {
660 return Err(WifiError::NotWellFormed);
661 }
662 }
663 Ok(())
664 }
665
666 fn encode_inner(&self, out: &mut [u8], with_credential: bool) -> Result<usize, WifiError> {
667 let credential: &[u8] = if with_credential {
668 self.credential
669 } else {
670 &[]
671 };
672 if self.ssid.len() > SSID_MAX_LEN {
673 return Err(WifiError::NotWellFormed);
674 }
675 let len = 11 + self.ssid.len() + credential.len();
676 if out.len() < len {
677 return Err(WifiError::BufferTooSmall);
678 }
679 out[0] = self.flags();
680 out[1] = self.security.code();
681 out[2] = self.max_clients;
682 out[3..5].copy_from_slice(&self.frequency_mhz.to_le_bytes());
683 out[5..9].copy_from_slice(&self.address);
684 out[9] = self.prefix;
685 out[10] = self.ssid.len() as u8;
686 out[11..11 + self.ssid.len()].copy_from_slice(self.ssid);
687 out[11 + self.ssid.len()..len].copy_from_slice(credential);
688 Ok(len)
689 }
690
691 pub fn encode(&self, out: &mut [u8]) -> Result<usize, WifiError> {
693 self.encode_inner(out, true)
694 }
695
696 pub fn encode_reported(&self, out: &mut [u8]) -> Result<usize, WifiError> {
698 self.encode_inner(out, false)
699 }
700
701 pub fn decode(bytes: &'a [u8]) -> Result<Self, WifiError> {
702 if bytes.len() < 11 {
703 return Err(WifiError::Truncated);
704 }
705 let flags = bytes[0];
706 if flags & !FLAG_HIDDEN != 0 {
707 return Err(WifiError::Malformed);
708 }
709 let security = SecurityMode::from_code(bytes[1]).ok_or(WifiError::Malformed)?;
710 let ssid_len = bytes[10] as usize;
711 if ssid_len > SSID_MAX_LEN {
712 return Err(WifiError::Malformed);
713 }
714 if bytes.len() < 11 + ssid_len {
715 return Err(WifiError::Truncated);
716 }
717 let mut address = [0u8; 4];
718 address.copy_from_slice(&bytes[5..9]);
719 Ok(Self {
720 hidden: flags & FLAG_HIDDEN != 0,
721 security,
722 max_clients: bytes[2],
723 frequency_mhz: le16(bytes, 3).ok_or(WifiError::Truncated)?,
724 address,
725 prefix: bytes[9],
726 ssid: &bytes[11..11 + ssid_len],
727 credential: &bytes[11 + ssid_len..],
728 })
729 }
730}
731
732#[derive(Clone, Copy, Debug, PartialEq, Eq)]
735pub struct ApClient {
736 pub mac: [u8; MAC_LEN],
738 pub rssi_dbm: i8,
740 pub address: [u8; 4],
743}
744
745impl ApClient {
746 pub fn has_address(&self) -> bool {
748 self.address != [0; 4]
749 }
750
751 pub fn encode(&self, out: &mut [u8]) -> Result<usize, WifiError> {
752 if out.len() < AP_CLIENT_LEN {
753 return Err(WifiError::BufferTooSmall);
754 }
755 out[0..6].copy_from_slice(&self.mac);
756 out[6] = self.rssi_dbm as u8;
757 out[7..11].copy_from_slice(&self.address);
758 Ok(AP_CLIENT_LEN)
759 }
760
761 pub fn decode(bytes: &[u8]) -> Result<Self, WifiError> {
762 if bytes.len() < AP_CLIENT_LEN {
763 return Err(WifiError::Truncated);
764 }
765 let mut mac = [0u8; MAC_LEN];
766 mac.copy_from_slice(&bytes[0..6]);
767 let mut address = [0u8; 4];
768 address.copy_from_slice(&bytes[7..11]);
769 Ok(Self {
770 mac,
771 rssi_dbm: bytes[6] as i8,
772 address,
773 })
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780
781 const SSID: &[u8] = b"umsh-test";
782
783 #[test]
784 fn minimum_security_is_a_credential_compatible_relation() {
785 use SecurityMode::*;
786 for minimum in [Open, Owe, Wpa, Wpa2, Wpa3] {
787 for offered in [
788 Open, Owe, Wpa, Wpa2, Wpa3, Wep, Wpa2Ent, Wpa3Ent, Wpa3Ent192,
789 ] {
790 let passphrase = matches!(
791 (minimum, offered),
792 (Open, Open | Owe)
793 | (Owe, Owe)
794 | (Wpa, Wpa | Wpa2 | Wpa3)
795 | (Wpa2, Wpa2 | Wpa3)
796 | (Wpa3, Wpa3)
797 );
798 assert_eq!(minimum.permits(offered, false), passphrase);
799 let pmk = matches!((minimum, offered), (Wpa, Wpa | Wpa2) | (Wpa2, Wpa2));
800 assert_eq!(minimum.permits(offered, true), pmk);
801 }
802 }
803 }
804
805 #[test]
806 fn strongest_selection_supports_legacy_wpa_without_lowering_stronger_profiles() {
807 use SecurityMode::*;
808 let supported = Open.bit() | Wpa.bit() | Wpa2.bit() | Wpa3.bit();
809 assert_eq!(
810 Wpa.strongest_compatible(Wpa.bit(), supported, false),
811 Some(Wpa)
812 );
813 assert_eq!(
814 Wpa.strongest_compatible(supported, supported, false),
815 Some(Wpa3)
816 );
817 assert_eq!(
818 Wpa.strongest_compatible(supported, supported, true),
819 Some(Wpa2)
820 );
821 assert_eq!(
822 Wpa.strongest_compatible(Wpa.bit(), supported, true),
823 Some(Wpa)
824 );
825 assert_eq!(Wpa2.strongest_compatible(Wpa.bit(), supported, false), None);
826 assert_eq!(
827 Wpa3.strongest_compatible(Wpa.bit() | Wpa2.bit(), supported, false),
828 None
829 );
830 assert_eq!(
831 Wpa2.strongest_compatible(Wpa3.bit(), supported, false),
832 Some(Wpa3)
833 );
834 assert_eq!(Wpa2.strongest_compatible(Wpa3.bit(), supported, true), None);
835 }
836
837 #[test]
838 fn security_codes_round_trip_strictly() {
839 for code in 0..=8u8 {
840 let mode = SecurityMode::from_code(code).expect("allocated");
841 assert_eq!(mode.code(), code);
842 }
843 assert_eq!(SecurityMode::from_code(9), None);
845 assert_eq!(SecurityMode::from_code(15), None);
846 assert_eq!(SecurityMode::from_code(16), None);
847 }
848
849 #[test]
850 fn every_mode_fits_the_sixteen_bit_scan_set() {
851 for code in 0..=8u8 {
852 let mode = SecurityMode::from_code(code).expect("allocated");
853 assert_ne!(mode.bit(), 0, "mode {code} has no bit in the set");
854 }
855 }
856
857 #[test]
858 fn only_four_modes_may_be_offered() {
859 assert!(SecurityMode::Open.may_be_offered());
860 assert!(SecurityMode::Owe.may_be_offered());
861 assert!(SecurityMode::Wpa2.may_be_offered());
862 assert!(SecurityMode::Wpa3.may_be_offered());
863 assert!(!SecurityMode::Wpa.may_be_offered());
866 assert!(!SecurityMode::Wep.may_be_offered());
867 assert!(!SecurityMode::Wpa2Ent.may_be_offered());
868 }
869
870 #[test]
871 fn a_network_entry_round_trips_with_its_passphrase() {
872 let entry = NetworkEntry {
873 hidden: true,
874 raw_key: false,
875 security: SecurityMode::Wpa2,
876 ssid: SSID,
877 credential: b"correct horse",
878 };
879 entry.validate().expect("well formed");
880 let mut buf = [0u8; NETWORK_ENTRY_MAX_LEN];
881 let len = entry.encode(&mut buf).expect("encodes");
882 assert_eq!(len, 3 + SSID.len() + 13);
883 assert_eq!(buf[0], FLAG_HIDDEN);
884 assert_eq!(buf[1], SecurityMode::Wpa2.code());
885 assert_eq!(buf[2], SSID.len() as u8);
886 assert_eq!(NetworkEntry::decode(&buf[..len]), Ok(entry));
887 }
888
889 #[test]
890 fn the_reported_form_carries_no_credential() {
891 let entry = NetworkEntry {
892 hidden: false,
893 raw_key: false,
894 security: SecurityMode::Wpa3,
895 ssid: SSID,
896 credential: b"a password nobody should read back",
897 };
898 let mut buf = [0u8; NETWORK_ENTRY_MAX_LEN];
899 let len = entry.encode_reported(&mut buf).expect("encodes");
900 assert_eq!(len, 3 + SSID.len());
901 let decoded = NetworkEntry::decode(&buf[..len]).expect("decodes");
902 assert!(decoded.credential.is_empty());
903 assert_eq!(decoded.ssid, SSID);
904 assert_eq!(decoded.security, SecurityMode::Wpa3);
905 assert!(!buf[..len].windows(8).any(|w| w == b"a passwo"));
907 }
908
909 #[test]
910 fn credential_bounds_follow_the_mode() {
911 let entry = |security, raw_key, credential| NetworkEntry {
912 hidden: false,
913 raw_key,
914 security,
915 ssid: SSID,
916 credential,
917 };
918 assert!(
920 entry(SecurityMode::Wpa2, false, b"1234567")
921 .validate()
922 .is_err()
923 );
924 assert!(
925 entry(SecurityMode::Wpa2, false, b"12345678")
926 .validate()
927 .is_ok()
928 );
929 assert!(
930 entry(SecurityMode::Wpa2, false, &[b'x'; 63])
931 .validate()
932 .is_ok()
933 );
934 assert!(
935 entry(SecurityMode::Wpa2, false, &[b'x'; 64])
936 .validate()
937 .is_err()
938 );
939 assert!(entry(SecurityMode::Wpa3, false, b"x").validate().is_ok());
942 assert!(
943 entry(SecurityMode::Wpa3, false, &[b'x'; 128])
944 .validate()
945 .is_ok()
946 );
947 assert!(
948 entry(SecurityMode::Wpa3, false, &[b'x'; 129])
949 .validate()
950 .is_err()
951 );
952 assert!(entry(SecurityMode::Open, false, b"").validate().is_ok());
954 assert!(
955 entry(SecurityMode::Open, false, b"anything")
956 .validate()
957 .is_err()
958 );
959 assert!(entry(SecurityMode::Owe, false, b"").validate().is_ok());
960 for mode in [
962 SecurityMode::Wep,
963 SecurityMode::Wpa2Ent,
964 SecurityMode::Wpa3Ent,
965 SecurityMode::Wpa3Ent192,
966 ] {
967 assert!(entry(mode, false, b"").validate().is_err());
968 assert!(entry(mode, false, b"secret12").validate().is_err());
969 }
970 }
971
972 #[test]
973 fn a_raw_key_stands_in_only_where_a_passphrase_is_derived() {
974 let entry = |security, credential| NetworkEntry {
975 hidden: false,
976 raw_key: true,
977 security,
978 ssid: SSID,
979 credential,
980 };
981 assert!(
982 entry(SecurityMode::Wpa2, &[0xAB; PMK_LEN])
983 .validate()
984 .is_ok()
985 );
986 assert!(
987 entry(SecurityMode::Wpa, &[0xAB; PMK_LEN])
988 .validate()
989 .is_ok()
990 );
991 assert!(entry(SecurityMode::Wpa2, &[0xAB; 31]).validate().is_err());
992 assert!(
995 entry(SecurityMode::Wpa3, &[0xAB; PMK_LEN])
996 .validate()
997 .is_err()
998 );
999 assert!(
1000 entry(SecurityMode::Open, &[0xAB; PMK_LEN])
1001 .validate()
1002 .is_err()
1003 );
1004 }
1005
1006 #[test]
1007 fn a_passphrase_is_utf8_without_a_terminator() {
1008 let entry = |credential| NetworkEntry {
1009 hidden: false,
1010 raw_key: false,
1011 security: SecurityMode::Wpa2,
1012 ssid: SSID,
1013 credential,
1014 };
1015 assert!(entry("passwörter".as_bytes()).validate().is_ok());
1016 assert!(entry(b"with\0a\0nul").validate().is_err());
1017 assert!(
1018 entry(&[0xFF, 0xFE, b'a', b'b', b'c', b'd', b'e', b'f'])
1019 .validate()
1020 .is_err()
1021 );
1022 }
1023
1024 #[test]
1025 fn an_empty_ssid_is_never_storable() {
1026 let entry = NetworkEntry {
1027 hidden: false,
1028 raw_key: false,
1029 security: SecurityMode::Open,
1030 ssid: b"",
1031 credential: b"",
1032 };
1033 assert_eq!(entry.validate(), Err(WifiError::NotWellFormed));
1034 let long = NetworkEntry {
1035 ssid: &[b'x'; 33],
1036 ..entry
1037 };
1038 assert_eq!(long.validate(), Err(WifiError::NotWellFormed));
1039 }
1040
1041 #[test]
1042 fn a_reserved_flag_bit_is_malformed() {
1043 let mut buf = [0u8; 8];
1044 buf[0] = 1 << 2;
1045 buf[1] = SecurityMode::Open.code();
1046 buf[2] = 1;
1047 buf[3] = b'x';
1048 assert_eq!(NetworkEntry::decode(&buf[..4]), Err(WifiError::Malformed));
1049 }
1050
1051 #[test]
1052 fn a_truncated_entry_is_not_a_malformed_one() {
1053 assert_eq!(NetworkEntry::decode(&[]), Err(WifiError::Truncated));
1054 assert_eq!(NetworkEntry::decode(&[0, 0]), Err(WifiError::Truncated));
1055 assert_eq!(
1057 NetworkEntry::decode(&[0, 0, 9, b'a', b'b', b'c', b'd']),
1058 Err(WifiError::Truncated)
1059 );
1060 }
1061
1062 #[test]
1063 fn a_scan_result_round_trips() {
1064 let result = ScanResult {
1065 modes: SecurityMode::Wpa2.bit() | SecurityMode::Wpa3.bit(),
1066 frequency_mhz: 2437,
1067 rssi_dbm: -67,
1068 bssid: [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01],
1069 ssid: SSID,
1070 };
1071 let mut buf = [0u8; SCAN_RESULT_MAX_LEN];
1072 let len = result.encode(&mut buf).expect("encodes");
1073 assert_eq!(len, 11 + SSID.len());
1074 assert_eq!(ScanResult::decode(&buf[..len]), Ok(result));
1075 assert!(result.offers(SecurityMode::Wpa2));
1077 assert!(result.offers(SecurityMode::Wpa3));
1078 assert!(!result.offers(SecurityMode::Open));
1079 assert!(result.modes_known());
1080 }
1081
1082 #[test]
1083 fn a_nameless_result_is_still_an_access_point() {
1084 let result = ScanResult {
1085 modes: 0,
1086 frequency_mhz: 5180,
1087 rssi_dbm: -80,
1088 bssid: [1, 2, 3, 4, 5, 6],
1089 ssid: b"",
1090 };
1091 let mut buf = [0u8; SCAN_RESULT_MAX_LEN];
1092 let len = result.encode(&mut buf).expect("encodes");
1093 assert_eq!(len, 11);
1094 let decoded = ScanResult::decode(&buf[..len]).expect("decodes");
1095 assert_eq!(decoded, result);
1096 assert!(!decoded.modes_known());
1099 }
1100
1101 #[test]
1102 fn a_link_carries_its_association_only_when_up() {
1103 let down = Link {
1104 state: LinkState::Connecting,
1105 reason: LinkReason::Auth,
1106 association: None,
1107 };
1108 let mut buf = [0u8; LINK_MAX_LEN];
1109 let len = down.encode(&mut buf).expect("encodes");
1110 assert_eq!(len, 2);
1111 assert_eq!(Link::decode(&buf[..len]), Ok(down));
1112
1113 let up = Link {
1114 state: LinkState::Up,
1115 reason: LinkReason::None,
1116 association: Some(Association {
1117 bssid: [0xA, 0xB, 0xC, 0xD, 0xE, 0xF],
1118 frequency_mhz: 5745,
1119 }),
1120 };
1121 let len = up.encode(&mut buf).expect("encodes");
1122 assert_eq!(len, 10);
1123 assert_eq!(Link::decode(&buf[..len]), Ok(up));
1124 }
1125
1126 #[test]
1127 fn an_up_link_without_an_association_is_truncated() {
1128 assert_eq!(
1129 Link::decode(&[LinkState::Up.code(), 0]),
1130 Err(WifiError::Truncated)
1131 );
1132 assert_eq!(Link::decode(&[9, 0]), Err(WifiError::Malformed));
1133 assert_eq!(Link::decode(&[0, 9]), Err(WifiError::Malformed));
1134 }
1135
1136 #[test]
1137 fn the_access_point_state_reports_where_it_landed() {
1138 let mut buf = [0u8; 3];
1139 let down = ApStatus {
1140 state: ApState::Down,
1141 frequency_mhz: None,
1142 };
1143 assert_eq!(down.encode(&mut buf), Ok(1));
1144 assert_eq!(ApStatus::decode(&buf[..1]), Ok(down));
1145
1146 let up = ApStatus {
1147 state: ApState::Up,
1148 frequency_mhz: Some(2412),
1149 };
1150 assert_eq!(up.encode(&mut buf), Ok(3));
1151 assert_eq!(ApStatus::decode(&buf[..3]), Ok(up));
1152 }
1153
1154 #[test]
1155 fn an_access_point_config_round_trips_and_redacts() {
1156 let config = ApConfig {
1157 hidden: false,
1158 security: SecurityMode::Wpa2,
1159 max_clients: 4,
1160 frequency_mhz: 2437,
1161 address: [192, 168, 4, 1],
1162 prefix: 24,
1163 ssid: SSID,
1164 credential: b"sharedsecret",
1165 };
1166 config.validate().expect("well formed");
1167 let mut buf = [0u8; AP_CONFIG_MAX_LEN];
1168 let len = config.encode(&mut buf).expect("encodes");
1169 assert_eq!(ApConfig::decode(&buf[..len]), Ok(config));
1170
1171 let len = config.encode_reported(&mut buf).expect("encodes");
1172 let reported = ApConfig::decode(&buf[..len]).expect("decodes");
1173 assert!(reported.credential.is_empty());
1174 assert_eq!(reported.ssid, SSID);
1175 assert_eq!(reported.address, [192, 168, 4, 1]);
1176 }
1177
1178 #[test]
1179 fn an_access_point_refuses_what_it_cannot_offer() {
1180 let base = ApConfig {
1181 hidden: false,
1182 security: SecurityMode::Wpa2,
1183 max_clients: 0,
1184 frequency_mhz: 0,
1185 address: [0; 4],
1186 prefix: 0,
1187 ssid: SSID,
1188 credential: b"sharedsecret",
1189 };
1190 assert!(base.validate().is_ok());
1192 assert!(base.uses_default_subnet());
1193 assert!(
1195 ApConfig {
1196 security: SecurityMode::Wpa,
1197 ..base
1198 }
1199 .validate()
1200 .is_err()
1201 );
1202 assert!(
1204 ApConfig {
1205 address: [169, 254, 1, 1],
1206 prefix: 24,
1207 ..base
1208 }
1209 .validate()
1210 .is_err()
1211 );
1212 assert!(
1213 ApConfig {
1214 address: [192, 168, 4, 1],
1215 prefix: 31,
1216 ..base
1217 }
1218 .validate()
1219 .is_err()
1220 );
1221 assert!(
1222 ApConfig {
1223 address: [192, 168, 4, 1],
1224 prefix: 24,
1225 ..base
1226 }
1227 .validate()
1228 .is_ok()
1229 );
1230 }
1231
1232 #[test]
1233 fn an_access_point_client_round_trips() {
1234 let client = ApClient {
1235 mac: [2, 4, 6, 8, 10, 12],
1236 rssi_dbm: -45,
1237 address: [192, 168, 4, 37],
1238 };
1239 let mut buf = [0u8; AP_CLIENT_LEN];
1240 assert_eq!(client.encode(&mut buf), Ok(AP_CLIENT_LEN));
1241 assert_eq!(ApClient::decode(&buf), Ok(client));
1242 assert!(client.has_address());
1243
1244 let unleased = ApClient {
1245 address: [0; 4],
1246 ..client
1247 };
1248 assert!(!unleased.has_address());
1249 }
1250
1251 #[test]
1252 fn encoders_report_a_buffer_too_small_rather_than_truncating() {
1253 let entry = NetworkEntry {
1254 hidden: false,
1255 raw_key: false,
1256 security: SecurityMode::Open,
1257 ssid: SSID,
1258 credential: b"",
1259 };
1260 let mut small = [0u8; 4];
1261 assert_eq!(entry.encode(&mut small), Err(WifiError::BufferTooSmall));
1262 let mut client = [0u8; 10];
1263 assert_eq!(
1264 ApClient {
1265 mac: [0; 6],
1266 rssi_dbm: 0,
1267 address: [0; 4]
1268 }
1269 .encode(&mut client),
1270 Err(WifiError::BufferTooSmall)
1271 );
1272 }
1273
1274 #[test]
1275 fn a_decoder_tolerates_octets_a_later_revision_appends() {
1276 let mut buf = [0u8; 16];
1279 buf[0] = LinkState::Up.code();
1280 buf[10] = 0xFF;
1281 buf[11] = 0xFF;
1282 let decoded = Link::decode(&buf[..12]).expect("decodes");
1283 assert_eq!(decoded.state, LinkState::Up);
1284 assert!(decoded.association.is_some());
1285 }
1286}