From 89ad84407a3571bb1eaa07a32a95cef1d8c6cf42 Mon Sep 17 00:00:00 2001 From: jasta Date: Tue, 14 Feb 2023 17:17:51 -0800 Subject: [PATCH 1/7] Add WiFi Easy Connect (DPP) wrappers and associated example --- Cargo.toml | 4 +- examples/wifi_dpp_setup.rs | 76 +++++++++++++ sdkconfig.defaults | 8 ++ src/lib.rs | 8 ++ src/wifi_dpp.rs | 223 +++++++++++++++++++++++++++++++++++++ 5 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 examples/wifi_dpp_setup.rs create mode 100644 sdkconfig.defaults create mode 100644 src/wifi_dpp.rs diff --git a/Cargo.toml b/Cargo.toml index 8cb40586b19..1a0f472828d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ log = { version = "0.4", default-features = false } uncased = "0.9.7" anyhow = { version = "1", default-features = false, optional = true } # Only used by the deprecated httpd module embedded-svc = { version = "0.24", default-features = false } -esp-idf-sys = { version = "0.32.1", default-features = false, features = ["native"] } -esp-idf-hal = { version = "0.40", default-features = false, features = ["esp-idf-sys"] } +esp-idf-sys = { path = "/home/jasta/software/esp-idf-sys", default-features = false, features = ["native"] } +esp-idf-hal = { path = "/home/jasta/software/esp-idf-hal", default-features = false, features = ["esp-idf-sys"] } embassy-sync = { version = "0.1", optional = true } embassy-time = { version = "0.1", optional = true, features = ["tick-hz-1_000_000"] } diff --git a/examples/wifi_dpp_setup.rs b/examples/wifi_dpp_setup.rs new file mode 100644 index 00000000000..57c59cf6c94 --- /dev/null +++ b/examples/wifi_dpp_setup.rs @@ -0,0 +1,76 @@ +//! Example using Wi-Fi Easy Connect (DPP) to get a device onto a Wi-Fi network +//! without hardcoding credentials. + +extern crate core; + +use esp_idf_hal as _; + +use std::time::Duration; +use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::log::EspLogger; +use esp_idf_svc::nvs::EspDefaultNvsPartition; +use esp_idf_svc::wifi::{EspWifi, WifiWait}; +use esp_idf_sys::EspError; +use log::{error, info, LevelFilter, warn}; +use esp_idf_svc::wifi_dpp::EspDppBootstrapper; + +fn main() { + esp_idf_sys::link_patches(); + + EspLogger::initialize_default(); + + let peripherals = Peripherals::take().unwrap(); + let sysloop = EspSystemEventLoop::take().unwrap(); + let nvs = EspDefaultNvsPartition::take().unwrap(); + + let mut wifi = EspWifi::new(peripherals.modem, sysloop.clone(), Some(nvs)).unwrap(); + + if !wifi_has_sta_config(&wifi).unwrap() { + info!("No existing STA config, let's try DPP..."); + let config = dpp_listen_forever(&mut wifi).unwrap(); + info!("Got config: {config:?}"); + wifi.set_configuration(&Configuration::Client(config)).unwrap(); + } + + wifi.start().unwrap(); + + let timeout = Duration::from_secs(60); + loop { + let ssid = match wifi.get_configuration().unwrap() { + Configuration::None => None, + Configuration::Client(ap) => Some(ap.ssid), + Configuration::AccessPoint(_) => None, + Configuration::Mixed(_, _) => None, + }.unwrap(); + info!("Connecting to {ssid}..."); + wifi.connect().unwrap(); + let waiter = WifiWait::new(&sysloop).unwrap(); + let is_connected = waiter.wait_with_timeout(timeout, || wifi.is_connected().unwrap()); + if is_connected { + info!("Connected!"); + waiter.wait(|| !wifi.is_connected().unwrap()); + warn!("Got disconnected, connecting again..."); + } else { + error!("Failed to connect after {}s, trying again...", timeout.as_secs()); + } + } +} + +fn wifi_has_sta_config(wifi: &EspWifi) -> Result { + match wifi.get_configuration()? { + Configuration::Client(c) => Ok(!c.ssid.is_empty()), + _ => Ok(false), + } +} + +fn dpp_listen_forever(wifi: &mut EspWifi) -> Result { + let mut dpp = EspDppBootstrapper::new(wifi)?; + let channels: Vec<_> = (1..=11).collect(); + let bootstrapped = dpp.gen_qrcode(&channels, None, None)?; + println!("Got: {}", bootstrapped.data.0); + println!("(use a QR code generator and scan the code in the Wi-Fi setup flow on your phone)"); + + bootstrapped.listen_forever() +} diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 00000000000..dfaeb82ac35 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,8 @@ +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_LOG_MAXIMUM_LEVEL_INFO=y + +CONFIG_WPA_DPP_SUPPORT=y + +CONFIG_ESP_MAIN_TASK_STACK_SIZE=15000 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=12000 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=8000 diff --git a/src/lib.rs b/src/lib.rs index c4b9e8d0b38..7a65e9b1cb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,14 @@ #[macro_use] extern crate alloc; +#[cfg(all( + feature = "alloc", + esp_idf_comp_wpa_supplicant_enabled, + any( + esp_idf_esp_wifi_dpp_support, + esp_idf_wpa_dpp_support + )))] +pub mod wifi_dpp; pub mod errors; #[cfg(all( feature = "alloc", diff --git a/src/wifi_dpp.rs b/src/wifi_dpp.rs new file mode 100644 index 00000000000..32a1faa8755 --- /dev/null +++ b/src/wifi_dpp.rs @@ -0,0 +1,223 @@ +//! Wi-Fi Easy Connect (DPP) support +//! +//! To use this feature, you must add CONFIG_WPA_DPP_SUPPORT=y to your sdkconfig. + +use ::log::*; + +use std::ffi::{c_char, CStr, CString}; +use std::fmt::Write; +use std::ops::Deref; +use std::ptr; +use std::sync::mpsc::{Receiver, sync_channel, SyncSender}; +use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; +use esp_idf_sys::*; +use esp_idf_sys::EspError; +use crate::private::common::Newtype; +use crate::private::mutex; +use crate::wifi::EspWifi; + +static EVENTS_TX: mutex::Mutex>> = + mutex::Mutex::wrap(mutex::RawMutex::new(), None); + +pub struct EspDppBootstrapper<'d, 'w> { + wifi: &'d mut EspWifi<'w>, + events_rx: Receiver, +} + +impl<'d, 'w> EspDppBootstrapper<'d, 'w> { + pub fn new(wifi: &'d mut EspWifi<'w>) -> Result { + if wifi.is_started()? { + wifi.disconnect()?; + wifi.stop()?; + } + + Self::init(wifi) + } + + fn init(wifi: &'d mut EspWifi<'w>) -> Result { + let (events_tx, events_rx) = sync_channel(1); + let mut dpp_event_relay = EVENTS_TX.lock(); + *dpp_event_relay = Some(events_tx); + drop(dpp_event_relay); + esp!(unsafe { esp_supp_dpp_init(Some(Self::dpp_event_cb_unsafe)) })?; + Ok(Self { + wifi, + events_rx, + }) + } + + /// Generate a QR code that can be scanned by a mobile phone or other configurator + /// to securely provide us with the Wi-Fi credentials. Must invoke a listen API on the returned + /// bootstrapped instance (e.g. [EspDppBootstrapped::listen_once]) or scanning the + /// QR code will not be able to deliver the credentials to us. + /// + /// Important implementation notes: + /// + /// 1. You must provide _all_ viable channels that the AP could be using + /// in order to successfully acquire credentials! For example, in the US, you can use + /// `(1..=11).collect()`. + /// + /// 2. The WiFi driver will be forced started and with a default STA config unless the + /// state is set-up ahead of time. It's unclear if the AuthMethod that you select + /// for this STA config affects the results. + pub fn gen_qrcode<'b>( + &'b mut self, + channels: &[u8], + key: Option<&[u8; 32]>, + associated_data: Option<&[u8]> + ) -> Result, EspError> { + let mut channels_str = channels.into_iter() + .fold(String::new(), |mut a, c| { + write!(a, "{c},").unwrap(); + a + }); + channels_str.pop(); + let channels_cstr = CString::new(channels_str).unwrap(); + let key_ascii_cstr = key.map(|k| { + let result = k.iter() + .fold(String::new(), |mut a, b| { + write!(a, "{b:02X}").unwrap(); + a + }); + CString::new(result).unwrap() + }); + let associated_data_cstr = match associated_data { + Some(associated_data) => { + Some(CString::new(associated_data) + .map_err(|_| { + warn!("associated data contains an embedded NUL character!"); + EspError::from_infallible::() + })?) + } + None => None, + }; + debug!("dpp_bootstrap_gen..."); + esp!(unsafe { + esp_supp_dpp_bootstrap_gen( + channels_cstr.as_ptr(), + dpp_bootstrap_type_DPP_BOOTSTRAP_QR_CODE, + key_ascii_cstr.map_or_else(ptr::null, |x| x.as_ptr()), + associated_data_cstr.map_or_else(ptr::null, |x| x.as_ptr())) + })?; + let event = self.events_rx.recv() + .map_err(|_| { + warn!("Internal error receiving event!"); + EspError::from_infallible::() + })?; + debug!("dpp_bootstrap_gen got: {event:?}"); + match event { + DppEvent::UriReady(qrcode) => { + // Bit of a hack to put the wifi driver in the correct mode. + self.ensure_config_and_start()?; + Ok(EspDppBootstrapped:: { + events_rx: &self.events_rx, + data: QrCode(qrcode), + }) + } + _ => { + warn!("Got unexpected event: {event:?}"); + Err(EspError::from_infallible::()) + }, + } + } + + fn ensure_config_and_start(&mut self) -> Result { + let operating_config = match self.wifi.get_configuration()? { + Configuration::Client(c) => c, + _ => { + let fallback_config = ClientConfiguration::default(); + self.wifi.set_configuration(&Configuration::Client(fallback_config.clone()))?; + fallback_config + }, + }; + if !self.wifi.is_started()? { + self.wifi.start()?; + } + Ok(operating_config) + } + + unsafe extern "C" fn dpp_event_cb_unsafe( + evt: esp_supp_dpp_event_t, + data: *mut ::core::ffi::c_void + ) { + debug!("dpp_event_cb_unsafe: evt={evt}"); + let event = match evt { + esp_supp_dpp_event_t_ESP_SUPP_DPP_URI_READY => { + DppEvent::UriReady(CStr::from_ptr(data as *mut c_char).to_str().unwrap().into()) + }, + esp_supp_dpp_event_t_ESP_SUPP_DPP_CFG_RECVD => { + let config = data as *mut wifi_config_t; + // TODO: We're losing pmf_cfg.required=true setting due to missing + // information in ClientConfiguration. + DppEvent::ConfigurationReceived(Newtype((*config).sta).into()) + }, + esp_supp_dpp_event_t_ESP_SUPP_DPP_FAIL => { + DppEvent::Fail(EspError::from(data as esp_err_t).unwrap()) + } + _ => panic!(), + }; + dpp_event_cb(event) + } +} + +fn dpp_event_cb(event: DppEvent) { + match EVENTS_TX.lock().deref() { + Some(tx) => { + debug!("Sending: {event:?}"); + if let Err(e) = tx.try_send(event) { + error!("Cannot relay event: {e}"); + } + } + None => warn!("Got spurious {event:?} ???"), + } +} + + +#[derive(Debug)] +enum DppEvent { + UriReady(String), + ConfigurationReceived(ClientConfiguration), + Fail(EspError), +} + +impl<'d, 'w> Drop for EspDppBootstrapper<'d, 'w> { + fn drop(&mut self) { + unsafe { esp_supp_dpp_deinit() }; + } +} + +pub struct EspDppBootstrapped<'d, T> { + events_rx: &'d Receiver, + pub data: T, +} + +#[derive(Debug, Clone)] +pub struct QrCode(pub String); + +impl<'d, T> EspDppBootstrapped<'d, T> { + pub fn listen_once(&self) -> Result { + esp!(unsafe { esp_supp_dpp_start_listen() })?; + let event = self.events_rx.recv() + .map_err(|e| { + warn!("Internal receive error: {e}"); + EspError::from_infallible::() + })?; + match event { + DppEvent::ConfigurationReceived(config) => Ok(config), + DppEvent::Fail(e) => Err(e), + _ => { + warn!("Ignoring unexpected event {event:?}"); + Err(EspError::from_infallible::()) + } + } + } + + pub fn listen_forever(&self) -> Result { + loop { + match self.listen_once() { + Ok(config) => return Ok(config), + Err(e) => warn!("DPP error: {e}, trying again..."), + } + } + } +} From 45146119412a9334ad88005f58e842e67846c67e Mon Sep 17 00:00:00 2001 From: jasta Date: Fri, 17 Feb 2023 16:22:48 -0800 Subject: [PATCH 2/7] Removed local paths added by mistake, will use patch.crates-io from now one :) --- Cargo.toml | 4 ++-- sdkconfig.defaults | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 sdkconfig.defaults diff --git a/Cargo.toml b/Cargo.toml index 1a0f472828d..8cb40586b19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ log = { version = "0.4", default-features = false } uncased = "0.9.7" anyhow = { version = "1", default-features = false, optional = true } # Only used by the deprecated httpd module embedded-svc = { version = "0.24", default-features = false } -esp-idf-sys = { path = "/home/jasta/software/esp-idf-sys", default-features = false, features = ["native"] } -esp-idf-hal = { path = "/home/jasta/software/esp-idf-hal", default-features = false, features = ["esp-idf-sys"] } +esp-idf-sys = { version = "0.32.1", default-features = false, features = ["native"] } +esp-idf-hal = { version = "0.40", default-features = false, features = ["esp-idf-sys"] } embassy-sync = { version = "0.1", optional = true } embassy-time = { version = "0.1", optional = true, features = ["tick-hz-1_000_000"] } diff --git a/sdkconfig.defaults b/sdkconfig.defaults deleted file mode 100644 index dfaeb82ac35..00000000000 --- a/sdkconfig.defaults +++ /dev/null @@ -1,8 +0,0 @@ -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -CONFIG_LOG_MAXIMUM_LEVEL_INFO=y - -CONFIG_WPA_DPP_SUPPORT=y - -CONFIG_ESP_MAIN_TASK_STACK_SIZE=15000 -CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=12000 -CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=8000 From c4feaf32a17a0a2560a69e62ecb4f4451f3c1202 Mon Sep 17 00:00:00 2001 From: jasta Date: Sun, 19 Feb 2023 11:36:07 -0800 Subject: [PATCH 3/7] Removed wifi_dpp_setup example, can't get it to compile --- examples/wifi_dpp_setup.rs | 76 -------------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 examples/wifi_dpp_setup.rs diff --git a/examples/wifi_dpp_setup.rs b/examples/wifi_dpp_setup.rs deleted file mode 100644 index 57c59cf6c94..00000000000 --- a/examples/wifi_dpp_setup.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Example using Wi-Fi Easy Connect (DPP) to get a device onto a Wi-Fi network -//! without hardcoding credentials. - -extern crate core; - -use esp_idf_hal as _; - -use std::time::Duration; -use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_svc::eventloop::EspSystemEventLoop; -use esp_idf_svc::log::EspLogger; -use esp_idf_svc::nvs::EspDefaultNvsPartition; -use esp_idf_svc::wifi::{EspWifi, WifiWait}; -use esp_idf_sys::EspError; -use log::{error, info, LevelFilter, warn}; -use esp_idf_svc::wifi_dpp::EspDppBootstrapper; - -fn main() { - esp_idf_sys::link_patches(); - - EspLogger::initialize_default(); - - let peripherals = Peripherals::take().unwrap(); - let sysloop = EspSystemEventLoop::take().unwrap(); - let nvs = EspDefaultNvsPartition::take().unwrap(); - - let mut wifi = EspWifi::new(peripherals.modem, sysloop.clone(), Some(nvs)).unwrap(); - - if !wifi_has_sta_config(&wifi).unwrap() { - info!("No existing STA config, let's try DPP..."); - let config = dpp_listen_forever(&mut wifi).unwrap(); - info!("Got config: {config:?}"); - wifi.set_configuration(&Configuration::Client(config)).unwrap(); - } - - wifi.start().unwrap(); - - let timeout = Duration::from_secs(60); - loop { - let ssid = match wifi.get_configuration().unwrap() { - Configuration::None => None, - Configuration::Client(ap) => Some(ap.ssid), - Configuration::AccessPoint(_) => None, - Configuration::Mixed(_, _) => None, - }.unwrap(); - info!("Connecting to {ssid}..."); - wifi.connect().unwrap(); - let waiter = WifiWait::new(&sysloop).unwrap(); - let is_connected = waiter.wait_with_timeout(timeout, || wifi.is_connected().unwrap()); - if is_connected { - info!("Connected!"); - waiter.wait(|| !wifi.is_connected().unwrap()); - warn!("Got disconnected, connecting again..."); - } else { - error!("Failed to connect after {}s, trying again...", timeout.as_secs()); - } - } -} - -fn wifi_has_sta_config(wifi: &EspWifi) -> Result { - match wifi.get_configuration()? { - Configuration::Client(c) => Ok(!c.ssid.is_empty()), - _ => Ok(false), - } -} - -fn dpp_listen_forever(wifi: &mut EspWifi) -> Result { - let mut dpp = EspDppBootstrapper::new(wifi)?; - let channels: Vec<_> = (1..=11).collect(); - let bootstrapped = dpp.gen_qrcode(&channels, None, None)?; - println!("Got: {}", bootstrapped.data.0); - println!("(use a QR code generator and scan the code in the Wi-Fi setup flow on your phone)"); - - bootstrapped.listen_forever() -} From c1a0f0530e38fc2b8f16997eaf64eb220c9c30c0 Mon Sep 17 00:00:00 2001 From: jasta Date: Sun, 19 Feb 2023 11:36:16 -0800 Subject: [PATCH 4/7] Run cargo fix / cargo fmt --- src/lib.rs | 14 ++++---- src/wifi_dpp.rs | 95 ++++++++++++++++++++++--------------------------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7a65e9b1cb1..edeee753472 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,14 +25,6 @@ #[macro_use] extern crate alloc; -#[cfg(all( - feature = "alloc", - esp_idf_comp_wpa_supplicant_enabled, - any( - esp_idf_esp_wifi_dpp_support, - esp_idf_wpa_dpp_support - )))] -pub mod wifi_dpp; pub mod errors; #[cfg(all( feature = "alloc", @@ -100,6 +92,12 @@ pub mod tls; esp_idf_comp_esp_event_enabled, ))] pub mod wifi; +#[cfg(all( + feature = "alloc", + esp_idf_comp_wpa_supplicant_enabled, + any(esp_idf_esp_wifi_dpp_support, esp_idf_wpa_dpp_support) +))] +pub mod wifi_dpp; pub mod ws; mod private; diff --git a/src/wifi_dpp.rs b/src/wifi_dpp.rs index 32a1faa8755..044bf598ec4 100644 --- a/src/wifi_dpp.rs +++ b/src/wifi_dpp.rs @@ -4,17 +4,17 @@ use ::log::*; +use crate::private::common::Newtype; +use crate::private::mutex; +use crate::wifi::EspWifi; +use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; +use esp_idf_sys::EspError; +use esp_idf_sys::*; use std::ffi::{c_char, CStr, CString}; use std::fmt::Write; use std::ops::Deref; use std::ptr; -use std::sync::mpsc::{Receiver, sync_channel, SyncSender}; -use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; -use esp_idf_sys::*; -use esp_idf_sys::EspError; -use crate::private::common::Newtype; -use crate::private::mutex; -use crate::wifi::EspWifi; +use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; static EVENTS_TX: mutex::Mutex>> = mutex::Mutex::wrap(mutex::RawMutex::new(), None); @@ -40,10 +40,7 @@ impl<'d, 'w> EspDppBootstrapper<'d, 'w> { *dpp_event_relay = Some(events_tx); drop(dpp_event_relay); esp!(unsafe { esp_supp_dpp_init(Some(Self::dpp_event_cb_unsafe)) })?; - Ok(Self { - wifi, - events_rx, - }) + Ok(Self { wifi, events_rx }) } /// Generate a QR code that can be scanned by a mobile phone or other configurator @@ -64,46 +61,41 @@ impl<'d, 'w> EspDppBootstrapper<'d, 'w> { &'b mut self, channels: &[u8], key: Option<&[u8; 32]>, - associated_data: Option<&[u8]> + associated_data: Option<&[u8]>, ) -> Result, EspError> { - let mut channels_str = channels.into_iter() - .fold(String::new(), |mut a, c| { - write!(a, "{c},").unwrap(); - a - }); + let mut channels_str = channels.into_iter().fold(String::new(), |mut a, c| { + write!(a, "{c},").unwrap(); + a + }); channels_str.pop(); let channels_cstr = CString::new(channels_str).unwrap(); let key_ascii_cstr = key.map(|k| { - let result = k.iter() - .fold(String::new(), |mut a, b| { - write!(a, "{b:02X}").unwrap(); - a - }); + let result = k.iter().fold(String::new(), |mut a, b| { + write!(a, "{b:02X}").unwrap(); + a + }); CString::new(result).unwrap() }); let associated_data_cstr = match associated_data { - Some(associated_data) => { - Some(CString::new(associated_data) - .map_err(|_| { - warn!("associated data contains an embedded NUL character!"); - EspError::from_infallible::() - })?) - } + Some(associated_data) => Some(CString::new(associated_data).map_err(|_| { + warn!("associated data contains an embedded NUL character!"); + EspError::from_infallible::() + })?), None => None, }; debug!("dpp_bootstrap_gen..."); esp!(unsafe { - esp_supp_dpp_bootstrap_gen( - channels_cstr.as_ptr(), - dpp_bootstrap_type_DPP_BOOTSTRAP_QR_CODE, - key_ascii_cstr.map_or_else(ptr::null, |x| x.as_ptr()), - associated_data_cstr.map_or_else(ptr::null, |x| x.as_ptr())) - })?; - let event = self.events_rx.recv() - .map_err(|_| { - warn!("Internal error receiving event!"); - EspError::from_infallible::() - })?; + esp_supp_dpp_bootstrap_gen( + channels_cstr.as_ptr(), + dpp_bootstrap_type_DPP_BOOTSTRAP_QR_CODE, + key_ascii_cstr.map_or_else(ptr::null, |x| x.as_ptr()), + associated_data_cstr.map_or_else(ptr::null, |x| x.as_ptr()), + ) + })?; + let event = self.events_rx.recv().map_err(|_| { + warn!("Internal error receiving event!"); + EspError::from_infallible::() + })?; debug!("dpp_bootstrap_gen got: {event:?}"); match event { DppEvent::UriReady(qrcode) => { @@ -117,7 +109,7 @@ impl<'d, 'w> EspDppBootstrapper<'d, 'w> { _ => { warn!("Got unexpected event: {event:?}"); Err(EspError::from_infallible::()) - }, + } } } @@ -126,9 +118,10 @@ impl<'d, 'w> EspDppBootstrapper<'d, 'w> { Configuration::Client(c) => c, _ => { let fallback_config = ClientConfiguration::default(); - self.wifi.set_configuration(&Configuration::Client(fallback_config.clone()))?; + self.wifi + .set_configuration(&Configuration::Client(fallback_config.clone()))?; fallback_config - }, + } }; if !self.wifi.is_started()? { self.wifi.start()?; @@ -138,19 +131,19 @@ impl<'d, 'w> EspDppBootstrapper<'d, 'w> { unsafe extern "C" fn dpp_event_cb_unsafe( evt: esp_supp_dpp_event_t, - data: *mut ::core::ffi::c_void + data: *mut ::core::ffi::c_void, ) { debug!("dpp_event_cb_unsafe: evt={evt}"); let event = match evt { esp_supp_dpp_event_t_ESP_SUPP_DPP_URI_READY => { DppEvent::UriReady(CStr::from_ptr(data as *mut c_char).to_str().unwrap().into()) - }, + } esp_supp_dpp_event_t_ESP_SUPP_DPP_CFG_RECVD => { let config = data as *mut wifi_config_t; // TODO: We're losing pmf_cfg.required=true setting due to missing // information in ClientConfiguration. DppEvent::ConfigurationReceived(Newtype((*config).sta).into()) - }, + } esp_supp_dpp_event_t_ESP_SUPP_DPP_FAIL => { DppEvent::Fail(EspError::from(data as esp_err_t).unwrap()) } @@ -172,7 +165,6 @@ fn dpp_event_cb(event: DppEvent) { } } - #[derive(Debug)] enum DppEvent { UriReady(String), @@ -197,11 +189,10 @@ pub struct QrCode(pub String); impl<'d, T> EspDppBootstrapped<'d, T> { pub fn listen_once(&self) -> Result { esp!(unsafe { esp_supp_dpp_start_listen() })?; - let event = self.events_rx.recv() - .map_err(|e| { - warn!("Internal receive error: {e}"); - EspError::from_infallible::() - })?; + let event = self.events_rx.recv().map_err(|e| { + warn!("Internal receive error: {e}"); + EspError::from_infallible::() + })?; match event { DppEvent::ConfigurationReceived(config) => Ok(config), DppEvent::Fail(e) => Err(e), From e647aab5db5cee692c9edfde25fc82108b82660c Mon Sep 17 00:00:00 2001 From: jasta Date: Sun, 19 Feb 2023 17:34:39 -0800 Subject: [PATCH 5/7] Don't forward disconnect/stop errors during init --- src/wifi_dpp.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wifi_dpp.rs b/src/wifi_dpp.rs index 044bf598ec4..6d29f239591 100644 --- a/src/wifi_dpp.rs +++ b/src/wifi_dpp.rs @@ -26,10 +26,8 @@ pub struct EspDppBootstrapper<'d, 'w> { impl<'d, 'w> EspDppBootstrapper<'d, 'w> { pub fn new(wifi: &'d mut EspWifi<'w>) -> Result { - if wifi.is_started()? { - wifi.disconnect()?; - wifi.stop()?; - } + let _ = wifi.disconnect(); + let _ = wifi.stop(); Self::init(wifi) } From 96f80ae4b59f0b0d7ac24bd1ce9b22319264ed7c Mon Sep 17 00:00:00 2001 From: jasta Date: Tue, 21 Feb 2023 14:59:02 -0800 Subject: [PATCH 6/7] wifi_dpp: Reworked to not use std and generally simplified code --- src/private/waitable.rs | 39 +++ src/wifi.rs | 23 ++ src/wifi_dpp.rs | 515 +++++++++++++++++++++++++++++----------- 3 files changed, 435 insertions(+), 142 deletions(-) diff --git a/src/private/waitable.rs b/src/private/waitable.rs index 065d66b3d73..3c6f7f241c7 100644 --- a/src/private/waitable.rs +++ b/src/private/waitable.rs @@ -58,6 +58,22 @@ where } } + pub fn wait_while_and_get_mut( + &self, + condition: impl Fn(&T) -> bool, + getter: impl Fn(&mut T) -> Q, + ) -> Q { + let mut state = self.state.lock(); + + loop { + if !condition(&state) { + return getter(&mut state); + } + + state = self.cvar.wait(state); + } + } + pub fn wait_timeout_while_and_get( &self, dur: Duration, @@ -80,4 +96,27 @@ where } } } + + pub fn wait_timeout_while_and_get_mut( + &self, + dur: Duration, + condition: impl Fn(&T) -> bool, + getter: impl Fn(&mut T) -> Q, + ) -> (bool, Q) { + let mut state = self.state.lock(); + + loop { + if !condition(&state) { + return (false, getter(&mut state)); + } + + let (new_state, timeout) = self.cvar.wait_timeout(state, dur); + + state = new_state; + + if timeout { + return (true, getter(&mut state)); + } + } + } } diff --git a/src/wifi.rs b/src/wifi.rs index c91f2602bb2..0b15e68e785 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -31,6 +31,13 @@ use crate::private::cstr::*; use crate::private::mutex; use crate::private::waitable::*; +#[cfg(all( + feature = "alloc", + esp_idf_comp_wpa_supplicant_enabled, + any(esp_idf_esp_wifi_dpp_support, esp_idf_wpa_dpp_support) +))] +use crate::wifi_dpp::{EspWifiDpp, QrCode}; + pub mod config { use core::time::Duration; @@ -1122,6 +1129,22 @@ impl<'d> EspWifi<'d> { Ok(()) } + + /// Generate a QR code that can be used with a Wi-Fi Easy Connect compatible + /// configurator (e.g. a smart phone) to provision the MCU. + #[cfg(all( + feature = "alloc", + esp_idf_comp_wpa_supplicant_enabled, + any(esp_idf_esp_wifi_dpp_support, esp_idf_wpa_dpp_support) + ))] + pub fn dpp_generate_qrcode( + &mut self, + channels: &[u8], + key: Option<&[u8; 32]>, + associated_data: Option<&[u8]> + ) -> Result, EspError> { + EspWifiDpp::generate_qrcode(self, channels, key, associated_data) + } } #[cfg(esp_idf_comp_esp_netif_enabled)] diff --git a/src/wifi_dpp.rs b/src/wifi_dpp.rs index 6d29f239591..5a1562bb4bd 100644 --- a/src/wifi_dpp.rs +++ b/src/wifi_dpp.rs @@ -1,79 +1,273 @@ //! Wi-Fi Easy Connect (DPP) support //! -//! To use this feature, you must add CONFIG_WPA_DPP_SUPPORT=y to your sdkconfig. +//! # Example +//! +//! Note that to use this feature, you must add CONFIG_WPA_DPP_SUPPORT=y to your sdkconfig +//! +//! ```no_run +//! use esp_idf_hal::peripherals::Peripherals; +//! use esp_idf_svc::eventloop::EspSystemEventLoop; +//! use esp_idf_svc::nvs::EspDefaultNvsPartition; +//! use esp_idf_svc::wifi::EspWifi; +//! use esp_idf_svc::wifi_dpp::EspWifiDpp; +//! +//! let peripherals = Peripherals::take().unwrap(); +//! let sysloop = EspSystemEventLoop::take()?; +//! let nvs = EspDefaultNvsPartition::take()?; +//! let mut wifi = EspWifi::new(peripherals.modem, sysloop, Some(nvs))?; +//! +//! let channels = [6]; +//! // Test key, please use secure keys for your project (or None to generate one on the fly)! +//! let privkey = Some([ +//! 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, +//! 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, +//! ]); +//! let associated_data = None; +//! loop { +//! let dpp = EspWifiDpp::generate_qrcode( +//! &mut wifi, +//! &channels, +//! privkey.as_ref(), +//! associated_data)?; +//! log::info!("Got QR code text: {}", dpp.get_bootstrapped_data().0); +//! +//! match dpp.start_listen()?.wait_for_credentials() { +//! Ok(c) => break, +//! Err(e) => { +//! // Will generate the same QR code again since the inputs have not changed... +//! log::error!("DPP error: {e}, bootstrapping again..."); +//! } +//! } +//! } +//! ``` + +use core::fmt::Write; +use core::marker::PhantomData; +use core::mem; +use core::ops::{Deref, DerefMut}; +use core::ptr; +use core::time::Duration; +use alloc::sync::Arc; +use alloc::sync::Weak; use ::log::*; +use embedded_svc::wifi::{ClientConfiguration, Configuration}; +use esp_idf_sys::*; +use esp_idf_sys::EspError; use crate::private::common::Newtype; -use crate::private::mutex; +use crate::private::cstr::*; +use crate::private::mutex::{Mutex, RawMutex}; +use crate::private::waitable::Waitable; use crate::wifi::EspWifi; -use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; -use esp_idf_sys::EspError; -use esp_idf_sys::*; -use std::ffi::{c_char, CStr, CString}; -use std::fmt::Write; -use std::ops::Deref; -use std::ptr; -use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; - -static EVENTS_TX: mutex::Mutex>> = - mutex::Mutex::wrap(mutex::RawMutex::new(), None); - -pub struct EspDppBootstrapper<'d, 'w> { - wifi: &'d mut EspWifi<'w>, - events_rx: Receiver, + +/// Global singleton that proves we can't have imbalanced access to esp_supp_dpp_init/deinit. +/// This is statically enforced through requiring a mutable borrow of EspWifi in the API. +struct DppInitialized { + /// Holds the most recently received callback event that is yet to be processed/handled. + pending: Waitable>, } -impl<'d, 'w> EspDppBootstrapper<'d, 'w> { - pub fn new(wifi: &'d mut EspWifi<'w>) -> Result { +/// Global weak reference so that we can respond to the stateless C callbacks provided in +/// esp_supp_dpp_init. +static DPP_INITIALIZED: Mutex>> = Mutex::wrap(RawMutex::new(), None); + +impl DppInitialized { + fn new(wifi: &mut EspWifi) -> Result { let _ = wifi.disconnect(); let _ = wifi.stop(); - Self::init(wifi) + info!("Initializing DPP..."); + esp!(unsafe { esp_supp_dpp_init(Some(Self::dpp_event_cb_unsafe)) })?; + + Ok(Self { + pending: Waitable::new(None), + }) } - fn init(wifi: &'d mut EspWifi<'w>) -> Result { - let (events_tx, events_rx) = sync_channel(1); - let mut dpp_event_relay = EVENTS_TX.lock(); - *dpp_event_relay = Some(events_tx); - drop(dpp_event_relay); - esp!(unsafe { esp_supp_dpp_init(Some(Self::dpp_event_cb_unsafe)) })?; - Ok(Self { wifi, events_rx }) + fn store_weak_global(self: &Arc) -> Result<(), EspError> { + let weak_self = Arc::downgrade(self); + match mem::replace(DPP_INITIALIZED.lock().deref_mut(), Some(weak_self)) { + Some(existing) if existing.upgrade().is_some() => { + warn!("DPP already initialized, please file a bug!"); + Err(EspError::from_infallible::()) + } + _ => Ok(()), + } } + fn clear_weak_global() { + mem::take(DPP_INITIALIZED.lock().deref_mut()); + } + + fn upgrade_weak_global() -> Option> { + match DPP_INITIALIZED.lock().deref() { + None => None, + Some(dpp_weak) => dpp_weak.upgrade(), + } + } + + #[allow(non_upper_case_globals)] + unsafe extern "C" fn dpp_event_cb_unsafe( + evt: esp_supp_dpp_event_t, + data: *mut ::core::ffi::c_void, + ) { + let event = match evt { + esp_supp_dpp_event_t_ESP_SUPP_DPP_URI_READY => { + match ptr::NonNull::new(data as *mut c_char) { + None => { + warn!("Unknown input error from esp_dpp: null uri provided!"); + Some(DppState::Fail(EspError::from_infallible::())) + }, + Some(ptr) => { + let rust_str = from_cstr_ptr(ptr.as_ptr()).into(); + Some(DppState::BootstrappedUriReady(rust_str)) + } + } + }, + esp_supp_dpp_event_t_ESP_SUPP_DPP_CFG_RECVD => { + let config = data as *mut wifi_config_t; + // TODO: We're losing pmf_cfg.required=true setting due to missing + // information in ClientConfiguration. + Some(DppState::ConfigurationReceived(Newtype((*config).sta).into())) + } + esp_supp_dpp_event_t_ESP_SUPP_DPP_FAIL => { + Some(DppState::Fail(EspError::from(data as esp_err_t).unwrap())) + } + _ => { + warn!("Unsupported DPP event: {evt}, ignoring..."); + None + } + }; + if let Some(event) = event { + if let Err(event) = Self::maybe_set_pending_state(event) { + warn!("Spurious DPP event after deinit: {event:?}"); + } + } + } + + fn maybe_set_pending_state(state: DppState) -> Result<(), DppState> { + if let Some(dpp) = Self::upgrade_weak_global() { + dpp.set_pending_state(state); + Ok(()) + } else { + Err(state) + } + } + + fn wait_for_next_state(&self) -> DppState { + self.pending + .wait_while_and_get_mut( + |state| state.is_none(), + |state| state.take().unwrap()) + } + + fn wait_for_next_state_with_timeout(&self, timeout: Duration) -> Option { + let (timeout, state_opt) = self.pending.wait_timeout_while_and_get_mut( + timeout, + |state| state.is_none(), + |state| state.take(), + ); + if timeout { + None + } else { + Some(state_opt.unwrap()) + } + } + + fn set_pending_state(&self, state: DppState) { + self.pending.get_mut(|s| { + *s = Some(state); + }); + self.pending.cvar.notify_all(); + } +} + +impl Drop for DppInitialized { + fn drop(&mut self) { + info!("Deinitializing DPP..."); + unsafe { esp_supp_dpp_deinit() }; + + DppInitialized::clear_weak_global(); + } +} + +pub struct EspWifiDpp<'d, 'w, T> { + /// Store the only strong reference to the initialized state in a struct that is guaranteed + /// to borrow EspWifi for its lifetime. This provides the compile-time guarantee that + /// we cannot initialize DPP twice. + dpp: Arc, + _phantom: PhantomData<&'d PhantomData<&'w ()>>, + + bootstrapped_data: T, +} + +impl<'d, 'w> EspWifiDpp<'d, 'w, QrCode> { /// Generate a QR code that can be scanned by a mobile phone or other configurator - /// to securely provide us with the Wi-Fi credentials. Must invoke a listen API on the returned - /// bootstrapped instance (e.g. [EspDppBootstrapped::listen_once]) or scanning the - /// QR code will not be able to deliver the credentials to us. - /// - /// Important implementation notes: + /// to securely provide Wi-Fi credentials. On success, the caller must invoke + /// [::start_listen] to actually start listening. To wait for the credentials to + /// become available, see [DppWait]. /// - /// 1. You must provide _all_ viable channels that the AP could be using - /// in order to successfully acquire credentials! For example, in the US, you can use - /// `(1..=11).collect()`. + /// Note that [EspWifi] is mutably borrowed for the lifecycle of this object to ensure + /// that conflicting usage of the WiFi driver does not occur concurrently with DPP. It is + /// not known the effect this would have and in general it is assumed to be unsafe. /// - /// 2. The WiFi driver will be forced started and with a default STA config unless the - /// state is set-up ahead of time. It's unclear if the AuthMethod that you select - /// for this STA config affects the results. - pub fn gen_qrcode<'b>( - &'b mut self, + /// * `wifi` - Mutable borrow for the lifetime of DPP to prevent concurrent usage of the Wi-Fi + /// driver. + /// * `channels` - List of channels to listen for DPP auth messages. + /// * `key` - (Optional) NIST P-256 private key to use when generating the QR code. This can + /// be useful for example so that the QR code can be printed and distributed with the device. + /// If omitted, a unique private key is generated on each invocation. Do not include PEM + /// or DER formatting data as it will be added automatically depending on which version of + /// ESP-IDF is being used. + /// * `associated_data` - (Optional) Arbitrary extra information to include with the QR + /// code that may be relevant to the configurator. + pub fn generate_qrcode( + wifi: &'d mut EspWifi<'w>, channels: &[u8], key: Option<&[u8; 32]>, associated_data: Option<&[u8]>, - ) -> Result, EspError> { + ) -> Result { + let dpp = Arc::new(DppInitialized::new(wifi)?); + dpp.store_weak_global()?; + + Self::do_bootstrap_gen(channels, key, associated_data)?; + match dpp.wait_for_next_state() { + DppState::BootstrappedUriReady(qrcode) => { + wifi.set_configuration(&Configuration::Client(Default::default()))?; + wifi.start()?; + + Ok(Self { + dpp, + bootstrapped_data: QrCode(qrcode), + _phantom: PhantomData, + }) + }, + DppState::Fail(e) => Err(e), + other => Err(unexpected_state(other)), + } + } + + fn do_bootstrap_gen( + channels: &[u8], + key: Option<&[u8; 32]>, + associated_data: Option<&[u8]>, + ) -> Result<(), EspError> { let mut channels_str = channels.into_iter().fold(String::new(), |mut a, c| { write!(a, "{c},").unwrap(); a }); channels_str.pop(); let channels_cstr = CString::new(channels_str).unwrap(); + let key_ascii_cstr = key.map(|k| { - let result = k.iter().fold(String::new(), |mut a, b| { - write!(a, "{b:02X}").unwrap(); + let result = frame_key(k).iter().fold(String::new(), |mut a, b| { + write!(a, "{b:02x}").unwrap(); a }); CString::new(result).unwrap() }); + let associated_data_cstr = match associated_data { Some(associated_data) => Some(CString::new(associated_data).map_err(|_| { warn!("associated data contains an embedded NUL character!"); @@ -81,132 +275,169 @@ impl<'d, 'w> EspDppBootstrapper<'d, 'w> { })?), None => None, }; - debug!("dpp_bootstrap_gen..."); + + info!("Bootstrapping DPP with: channels={channels_cstr:?}, key={key_ascii_cstr:?}"); esp!(unsafe { esp_supp_dpp_bootstrap_gen( channels_cstr.as_ptr(), dpp_bootstrap_type_DPP_BOOTSTRAP_QR_CODE, - key_ascii_cstr.map_or_else(ptr::null, |x| x.as_ptr()), - associated_data_cstr.map_or_else(ptr::null, |x| x.as_ptr()), + key_ascii_cstr.as_ref().map_or_else(ptr::null, |x| x.as_ptr()), + associated_data_cstr.as_ref().map_or_else(ptr::null, |x| x.as_ptr()), ) })?; - let event = self.events_rx.recv().map_err(|_| { - warn!("Internal error receiving event!"); - EspError::from_infallible::() - })?; - debug!("dpp_bootstrap_gen got: {event:?}"); - match event { - DppEvent::UriReady(qrcode) => { - // Bit of a hack to put the wifi driver in the correct mode. - self.ensure_config_and_start()?; - Ok(EspDppBootstrapped:: { - events_rx: &self.events_rx, - data: QrCode(qrcode), - }) - } - _ => { - warn!("Got unexpected event: {event:?}"); - Err(EspError::from_infallible::()) - } + + // Guarantees we get a compiler error if we mess up the lifetime... + drop(channels_cstr); + drop(key_ascii_cstr); + drop(associated_data_cstr); + + Ok(()) + } +} + +impl<'d, 'w, T> EspWifiDpp<'d, 'w, T> { + pub fn get_bootstrapped_data(&self) -> &T { + &self.bootstrapped_data + } + + pub fn start_listen(self) -> Result, EspError> { + EspWifiDppListener::start_listen(self) + } +} + +pub struct EspWifiDppListener<'d, 'w, T> { + bootstrapped: EspWifiDpp<'d, 'w, T>, +} + +impl<'d, 'w, T> EspWifiDppListener<'d, 'w, T> { + fn start_listen(bootstrapped: EspWifiDpp<'d, 'w, T>) -> Result { + info!("Starting DPP listener..."); + esp!(unsafe { esp_supp_dpp_start_listen() })?; + Ok(Self { + bootstrapped, + }) + } + + /// Blocking wait for credentials or a possibly retryable error. Note that user error + /// such as scanning the wrong QR code can trigger this error case. Retries are highly + /// recommended, and especially via [Self::attempt_retry]. + pub fn wait_for_credentials(&self) -> Result { + let next_state = self.bootstrapped.dpp.wait_for_next_state(); + self.handle_next_state(next_state) + } + + /// Blocking wait for credentials, a timeout, or a terminal error. If the timeout is + /// reached, `Err(None)` is returned. + pub fn wait_for_credentials_with_timeout( + &self, + timeout: Duration, + ) -> Result> { + match self.bootstrapped.dpp.wait_for_next_state_with_timeout(timeout) { + None => { + self.stop_listen(); + Err(None) + }, + Some(state) => Ok(self.handle_next_state(state)?), } } - fn ensure_config_and_start(&mut self) -> Result { - let operating_config = match self.wifi.get_configuration()? { - Configuration::Client(c) => c, - _ => { - let fallback_config = ClientConfiguration::default(); - self.wifi - .set_configuration(&Configuration::Client(fallback_config.clone()))?; - fallback_config + fn handle_next_state(&self, state: DppState) -> Result { + match state { + DppState::ConfigurationReceived(c) => Ok(c), + DppState::Fail(e) => Err(e), + DppState::Stopped => { + info!("Caller requested DPP stop listening!"); + Err(EspError::from_infallible::()) } - }; - if !self.wifi.is_started()? { - self.wifi.start()?; + other => { + self.stop_listen(); + Err(unexpected_state(other)) + }, } - Ok(operating_config) } - unsafe extern "C" fn dpp_event_cb_unsafe( - evt: esp_supp_dpp_event_t, - data: *mut ::core::ffi::c_void, - ) { - debug!("dpp_event_cb_unsafe: evt={evt}"); - let event = match evt { - esp_supp_dpp_event_t_ESP_SUPP_DPP_URI_READY => { - DppEvent::UriReady(CStr::from_ptr(data as *mut c_char).to_str().unwrap().into()) - } - esp_supp_dpp_event_t_ESP_SUPP_DPP_CFG_RECVD => { - let config = data as *mut wifi_config_t; - // TODO: We're losing pmf_cfg.required=true setting due to missing - // information in ClientConfiguration. - DppEvent::ConfigurationReceived(Newtype((*config).sta).into()) - } - esp_supp_dpp_event_t_ESP_SUPP_DPP_FAIL => { - DppEvent::Fail(EspError::from(data as esp_err_t).unwrap()) - } - _ => panic!(), - }; - dpp_event_cb(event) + /// Stop listening for credentials. If any callers are actively blocked waiting for credentials + /// they will be notified with EspError(ESP_ERR_INVALID_STATE). This method is not + /// necessary to call after [Self::wait_for_credentials] or + /// [Self::wait_for_credentials_with_timeout] returns as the esp_dpp API automatically + /// stops listening on success or failure. + pub fn stop_listen(&self) { + info!("Stopping DPP listener..."); + + // SAFETY: This function should be safe to call without any locks as it mostly just + // sets a flag to halt at next chance. + unsafe { esp_supp_dpp_stop_listen() }; + + self.bootstrapped.dpp.set_pending_state(DppState::Stopped); } -} -fn dpp_event_cb(event: DppEvent) { - match EVENTS_TX.lock().deref() { - Some(tx) => { - debug!("Sending: {event:?}"); - if let Err(e) = tx.try_send(event) { - error!("Cannot relay event: {e}"); - } + /// Attempt to retry after a transient failure in [Self::wait_for_credentials] (for example + /// if a user scanned a bogus QR code or there was a recoverable transmit error on the + /// channel). This API will fail at runtime if ESP-IDF is not patched with + /// https://github.com/espressif/esp-idf/pull/10865. + /// + /// On failure to retry, the caller is expected to bootstrap again in order to logically retry + /// however this will potentially lose state and generate a new QR code if any parameters + /// change (such as a new key being generated). + pub fn attempt_retry(self) -> Result, ()> { + if Self::is_start_listen_patched() { + Ok(self.bootstrapped) + } else { + Err(()) } - None => warn!("Got spurious {event:?} ???"), } + + // TODO: Sure would be nice to be able to write esp_idf_version >= 5.1... + #[cfg( + any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + ) + )] + fn is_start_listen_patched() -> bool { false } + + #[cfg( + not( + any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + ) + ) + )] + fn is_start_listen_patched() -> bool { true } +} + +fn unexpected_state(state: DppState) -> EspError { + warn!("Unexpected DPP state: {state:?}"); + EspError::from_infallible::() } #[derive(Debug)] -enum DppEvent { - UriReady(String), +enum DppState { + BootstrappedUriReady(String), ConfigurationReceived(ClientConfiguration), Fail(EspError), + Stopped, } -impl<'d, 'w> Drop for EspDppBootstrapper<'d, 'w> { - fn drop(&mut self) { - unsafe { esp_supp_dpp_deinit() }; - } -} +pub struct QrCode(pub String); -pub struct EspDppBootstrapped<'d, T> { - events_rx: &'d Receiver, - pub data: T, +#[cfg(esp_idf_version_major = "4")] +/// ESP-IDF 4.x internally framed the key inside esp_dpp APIs. +fn frame_key(unframed: &[u8; 32]) -> &[u8] { + unframed } -#[derive(Debug, Clone)] -pub struct QrCode(pub String); +#[cfg(not(esp_idf_version_major = "4"))] +/// ESP-IDF 5.x requires the caller put the key into the PEM format +fn frame_key(unframed: &[u8; 32]) -> Vec { + let prefix = [0x30, 0x31, 0x02, 0x01, 0x01, 0x04, 0x20]; + let postfix = [0xa0, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]; -impl<'d, T> EspDppBootstrapped<'d, T> { - pub fn listen_once(&self) -> Result { - esp!(unsafe { esp_supp_dpp_start_listen() })?; - let event = self.events_rx.recv().map_err(|e| { - warn!("Internal receive error: {e}"); - EspError::from_infallible::() - })?; - match event { - DppEvent::ConfigurationReceived(config) => Ok(config), - DppEvent::Fail(e) => Err(e), - _ => { - warn!("Ignoring unexpected event {event:?}"); - Err(EspError::from_infallible::()) - } - } - } + let mut ret = Vec::with_capacity(prefix.len() + unframed.len() + postfix.len()); + ret.extend(prefix); + ret.extend(unframed); + ret.extend(postfix); - pub fn listen_forever(&self) -> Result { - loop { - match self.listen_once() { - Ok(config) => return Ok(config), - Err(e) => warn!("DPP error: {e}, trying again..."), - } - } - } + ret } From a6132ead7e5b0848e72cc5605df19b04a67f0cbb Mon Sep 17 00:00:00 2001 From: jasta Date: Wed, 1 Mar 2023 19:34:54 -0800 Subject: [PATCH 7/7] cargo fmt --- src/wifi.rs | 2 +- src/wifi_dpp.rs | 82 +++++++++++++++++++++++++++---------------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/wifi.rs b/src/wifi.rs index 0b15e68e785..915ac3e1810 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -1141,7 +1141,7 @@ impl<'d> EspWifi<'d> { &mut self, channels: &[u8], key: Option<&[u8; 32]>, - associated_data: Option<&[u8]> + associated_data: Option<&[u8]>, ) -> Result, EspError> { EspWifiDpp::generate_qrcode(self, channels, key, associated_data) } diff --git a/src/wifi_dpp.rs b/src/wifi_dpp.rs index 5a1562bb4bd..619d43ae310 100644 --- a/src/wifi_dpp.rs +++ b/src/wifi_dpp.rs @@ -41,19 +41,19 @@ //! } //! ``` +use alloc::sync::Arc; +use alloc::sync::Weak; use core::fmt::Write; use core::marker::PhantomData; use core::mem; use core::ops::{Deref, DerefMut}; use core::ptr; use core::time::Duration; -use alloc::sync::Arc; -use alloc::sync::Weak; use ::log::*; use embedded_svc::wifi::{ClientConfiguration, Configuration}; -use esp_idf_sys::*; use esp_idf_sys::EspError; +use esp_idf_sys::*; use crate::private::common::Newtype; use crate::private::cstr::*; @@ -117,19 +117,23 @@ impl DppInitialized { match ptr::NonNull::new(data as *mut c_char) { None => { warn!("Unknown input error from esp_dpp: null uri provided!"); - Some(DppState::Fail(EspError::from_infallible::())) - }, + Some(DppState::Fail(EspError::from_infallible::< + ESP_ERR_INVALID_ARG, + >())) + } Some(ptr) => { let rust_str = from_cstr_ptr(ptr.as_ptr()).into(); Some(DppState::BootstrappedUriReady(rust_str)) } } - }, + } esp_supp_dpp_event_t_ESP_SUPP_DPP_CFG_RECVD => { let config = data as *mut wifi_config_t; // TODO: We're losing pmf_cfg.required=true setting due to missing // information in ClientConfiguration. - Some(DppState::ConfigurationReceived(Newtype((*config).sta).into())) + Some(DppState::ConfigurationReceived( + Newtype((*config).sta).into(), + )) } esp_supp_dpp_event_t_ESP_SUPP_DPP_FAIL => { Some(DppState::Fail(EspError::from(data as esp_err_t).unwrap())) @@ -157,9 +161,7 @@ impl DppInitialized { fn wait_for_next_state(&self) -> DppState { self.pending - .wait_while_and_get_mut( - |state| state.is_none(), - |state| state.take().unwrap()) + .wait_while_and_get_mut(|state| state.is_none(), |state| state.take().unwrap()) } fn wait_for_next_state_with_timeout(&self, timeout: Duration) -> Option { @@ -242,7 +244,7 @@ impl<'d, 'w> EspWifiDpp<'d, 'w, QrCode> { bootstrapped_data: QrCode(qrcode), _phantom: PhantomData, }) - }, + } DppState::Fail(e) => Err(e), other => Err(unexpected_state(other)), } @@ -281,8 +283,12 @@ impl<'d, 'w> EspWifiDpp<'d, 'w, QrCode> { esp_supp_dpp_bootstrap_gen( channels_cstr.as_ptr(), dpp_bootstrap_type_DPP_BOOTSTRAP_QR_CODE, - key_ascii_cstr.as_ref().map_or_else(ptr::null, |x| x.as_ptr()), - associated_data_cstr.as_ref().map_or_else(ptr::null, |x| x.as_ptr()), + key_ascii_cstr + .as_ref() + .map_or_else(ptr::null, |x| x.as_ptr()), + associated_data_cstr + .as_ref() + .map_or_else(ptr::null, |x| x.as_ptr()), ) })?; @@ -313,9 +319,7 @@ impl<'d, 'w, T> EspWifiDppListener<'d, 'w, T> { fn start_listen(bootstrapped: EspWifiDpp<'d, 'w, T>) -> Result { info!("Starting DPP listener..."); esp!(unsafe { esp_supp_dpp_start_listen() })?; - Ok(Self { - bootstrapped, - }) + Ok(Self { bootstrapped }) } /// Blocking wait for credentials or a possibly retryable error. Note that user error @@ -332,11 +336,15 @@ impl<'d, 'w, T> EspWifiDppListener<'d, 'w, T> { &self, timeout: Duration, ) -> Result> { - match self.bootstrapped.dpp.wait_for_next_state_with_timeout(timeout) { + match self + .bootstrapped + .dpp + .wait_for_next_state_with_timeout(timeout) + { None => { self.stop_listen(); Err(None) - }, + } Some(state) => Ok(self.handle_next_state(state)?), } } @@ -352,7 +360,7 @@ impl<'d, 'w, T> EspWifiDppListener<'d, 'w, T> { other => { self.stop_listen(); Err(unexpected_state(other)) - }, + } } } @@ -388,23 +396,21 @@ impl<'d, 'w, T> EspWifiDppListener<'d, 'w, T> { } // TODO: Sure would be nice to be able to write esp_idf_version >= 5.1... - #[cfg( - any( - esp_idf_version_major = "4", - all(esp_idf_version_major = "5", esp_idf_version_minor = "0") - ) - )] - fn is_start_listen_patched() -> bool { false } - - #[cfg( - not( - any( - esp_idf_version_major = "4", - all(esp_idf_version_major = "5", esp_idf_version_minor = "0") - ) - ) - )] - fn is_start_listen_patched() -> bool { true } + #[cfg(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + ))] + fn is_start_listen_patched() -> bool { + false + } + + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + )))] + fn is_start_listen_patched() -> bool { + true + } } fn unexpected_state(state: DppState) -> EspError { @@ -432,7 +438,9 @@ fn frame_key(unframed: &[u8; 32]) -> &[u8] { /// ESP-IDF 5.x requires the caller put the key into the PEM format fn frame_key(unframed: &[u8; 32]) -> Vec { let prefix = [0x30, 0x31, 0x02, 0x01, 0x01, 0x04, 0x20]; - let postfix = [0xa0, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]; + let postfix = [ + 0xa0, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, + ]; let mut ret = Vec::with_capacity(prefix.len() + unframed.len() + postfix.len()); ret.extend(prefix);