/ firmware / src / networking / wifi / credentials.rs
credentials.rs
  1  use embedded_storage::nor_flash::NorFlash;
  2  use embedded_storage::ReadStorage;
  3  use esp_storage::FlashStorage;
  4  
  5  pub const DEFAULT_SSID: &str = env!("WIFI_SSID");
  6  pub const DEFAULT_PASSWORD: &str = env!("WIFI_PSK");
  7  
  8  const CREDENTIALS_MAGIC: u32 = 0xCE6A0001;
  9  const CREDENTIALS_OFFSET: usize = 0x1000;
 10  #[allow(dead_code, reason = "used by credential update API endpoint")]
 11  const CREDENTIALS_SECTOR_SIZE: usize = 4096;
 12  const SSID_MAX_LEN: usize = 32;
 13  const PASSWORD_MAX_LEN: usize = 64;
 14  
 15  #[repr(C)]
 16  struct CredentialsRecord {
 17      magic: u32,
 18      ssid_len: u8,
 19      password_len: u8,
 20      ssid: [u8; SSID_MAX_LEN],
 21      password: [u8; PASSWORD_MAX_LEN],
 22  }
 23  
 24  pub struct WifiCredentials {
 25      pub ssid: heapless::String<SSID_MAX_LEN>,
 26      pub password: heapless::String<PASSWORD_MAX_LEN>,
 27  }
 28  
 29  pub fn default_credentials() -> WifiCredentials {
 30      WifiCredentials {
 31          ssid: heapless::String::try_from(DEFAULT_SSID).unwrap(),
 32          password: heapless::String::try_from(DEFAULT_PASSWORD).unwrap(),
 33      }
 34  }
 35  
 36  pub fn read_from_flash(flash: &mut FlashStorage) -> Option<WifiCredentials> {
 37      let mut buffer = [0u8; size_of::<CredentialsRecord>()];
 38  
 39      if flash.read(CREDENTIALS_OFFSET as u32, &mut buffer).is_err() {
 40          return None;
 41      }
 42  
 43      let record: CredentialsRecord =
 44          unsafe { core::ptr::read_unaligned(buffer.as_ptr() as *const _) };
 45  
 46      if record.magic != CREDENTIALS_MAGIC {
 47          return None;
 48      }
 49  
 50      let ssid_len = record.ssid_len as usize;
 51      let password_len = record.password_len as usize;
 52  
 53      if ssid_len > SSID_MAX_LEN || password_len > PASSWORD_MAX_LEN || ssid_len == 0 {
 54          return None;
 55      }
 56  
 57      let mut ssid = heapless::String::new();
 58      for &byte in &record.ssid[..ssid_len] {
 59          if ssid.push(byte as char).is_err() {
 60              break;
 61          }
 62      }
 63  
 64      let mut password = heapless::String::new();
 65      for &byte in &record.password[..password_len] {
 66          if password.push(byte as char).is_err() {
 67              break;
 68          }
 69      }
 70  
 71      Some(WifiCredentials { ssid, password })
 72  }
 73  
 74  pub fn write_to_flash(flash: &mut FlashStorage, ssid: &str, password: &str) -> bool {
 75      if ssid.len() > SSID_MAX_LEN || password.len() > PASSWORD_MAX_LEN || ssid.is_empty() {
 76          return false;
 77      }
 78  
 79      let mut record = CredentialsRecord {
 80          magic: CREDENTIALS_MAGIC,
 81          ssid_len: ssid.len() as u8,
 82          password_len: password.len() as u8,
 83          ssid: [0u8; SSID_MAX_LEN],
 84          password: [0u8; PASSWORD_MAX_LEN],
 85      };
 86  
 87      record.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
 88      record.password[..password.len()].copy_from_slice(password.as_bytes());
 89  
 90      let mut buffer = [0xFFu8; CREDENTIALS_SECTOR_SIZE];
 91      let record_bytes = unsafe {
 92          core::slice::from_raw_parts(
 93              &record as *const CredentialsRecord as *const u8,
 94              size_of::<CredentialsRecord>(),
 95          )
 96      };
 97      buffer[..record_bytes.len()].copy_from_slice(record_bytes);
 98  
 99      if NorFlash::erase(
100          flash,
101          CREDENTIALS_OFFSET as u32,
102          (CREDENTIALS_OFFSET + CREDENTIALS_SECTOR_SIZE) as u32,
103      )
104      .is_err()
105      {
106          return false;
107      }
108  
109      if flash.write(CREDENTIALS_OFFSET as u32, &buffer).is_err() {
110          return false;
111      }
112  
113      if let Some(verified) = read_from_flash(flash) {
114          verified.ssid.as_str() == ssid && verified.password.as_str() == password
115      } else {
116          false
117      }
118  }