use crate::errors::Error;
use embedded_nal::nb;
use crate::error;
use crate::manager::{AccessPoint, AuthType, FirmwareInfo, IPConf, ScanResult};
use crate::manager::{Credentials, HostName, ProvisioningInfo};
use super::PingResult;
use super::StackError;
use super::WincClient;
use super::Xfer;
use crate::stack::socket_callbacks::WifiModuleState;
use crate::info;
const AP_CONNECT_TIMEOUT_MILLISECONDS: u32 = 60_000;
const AP_DISCONNECT_TIMEOUT_MILLISECONDS: u32 = 5_000;
#[cfg(not(test))]
const PROVISIONING_TIMEOUT: u32 = 60 * 1000;
#[cfg(test)]
const PROVISIONING_TIMEOUT: u32 = 1000;
impl<X: Xfer> WincClient<'_, X> {
pub fn heartbeat(&mut self) -> Result<(), StackError> {
self.dispatch_events()?;
Ok(())
}
pub fn start_wifi_module(&mut self) -> nb::Result<(), StackError> {
match self.callbacks.state {
WifiModuleState::Reset => {
self.callbacks.state = WifiModuleState::Starting;
self.manager.set_crc_state(true);
self.boot = Some(Default::default());
Err(nb::Error::WouldBlock)
}
WifiModuleState::Starting => {
if let Some(state) = self.boot.as_mut() {
let result = self
.manager
.boot_the_chip(state)
.map_err(|x| nb::Error::Other(StackError::WincWifiFail(x)))?;
if result {
self.callbacks.state = WifiModuleState::Unconnected;
self.boot = None;
return Ok(());
}
Err(nb::Error::WouldBlock)
} else {
Err(nb::Error::Other(StackError::InvalidState))
}
}
_ => Err(nb::Error::Other(StackError::InvalidState)),
}
}
fn connect_to_ap_impl(
&mut self,
connect_fn: impl FnOnce(&mut Self) -> Result<(), crate::errors::Error>,
) -> nb::Result<(), StackError> {
match self.callbacks.state {
WifiModuleState::Reset | WifiModuleState::Starting | WifiModuleState::Disconnecting => {
Err(nb::Error::Other(StackError::InvalidState))
}
WifiModuleState::Unconnected | WifiModuleState::Provisioning => {
self.operation_countdown = AP_CONNECT_TIMEOUT_MILLISECONDS;
self.callbacks.state = WifiModuleState::ConnectingToAp;
connect_fn(self).map_err(|x| nb::Error::Other(StackError::WincWifiFail(x)))?;
Err(nb::Error::WouldBlock)
}
WifiModuleState::ConnectingToAp => {
self.delay_us(self.poll_loop_delay_us); self.dispatch_events()?;
self.operation_countdown -= 1;
if self.operation_countdown == 0 {
return Err(nb::Error::Other(StackError::GeneralTimeout));
}
Err(nb::Error::WouldBlock)
}
WifiModuleState::ConnectionFailed => Err(nb::Error::Other(StackError::ApJoinFailed(
self.callbacks.connection_state.conn_error.take().unwrap(),
))),
WifiModuleState::ConnectedToAp => {
info!("connect_to_ap: got Connected to AP");
Ok(())
}
}
}
pub fn connect_to_saved_ap(&mut self) -> nb::Result<(), StackError> {
self.connect_to_ap_impl(|inner_self: &mut Self| inner_self.manager.send_default_connect())
}
pub fn connect_to_ap(
&mut self,
ssid: &str,
password: &str,
save_credentials: bool,
) -> nb::Result<(), StackError> {
self.connect_to_ap_impl(|inner_self: &mut Self| {
inner_self.manager.send_connect(
AuthType::WpaPSK,
ssid,
password,
0xFF,
!save_credentials,
)
})
}
pub fn scan(&mut self) -> nb::Result<u8, StackError> {
match &mut self.callbacks.connection_state.scan_number_aps {
None => {
const PASSIVE_SCAN_TIME: u16 = 1000;
self.manager
.send_scan(0xFF, PASSIVE_SCAN_TIME)
.map_err(|x| nb::Error::Other(StackError::WincWifiFail(x)))?;
self.callbacks.connection_state.scan_number_aps = Some(None);
}
Some(num_aps) => {
if let Some(num_aps) = num_aps.take() {
if let Some(err) = self.callbacks.connection_state.conn_error.take() {
return Err(nb::Error::Other(StackError::ApScanFailed(err)));
}
self.callbacks.connection_state.scan_number_aps = None;
return Ok(num_aps);
}
}
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn get_scan_result(&mut self, index: u8) -> nb::Result<ScanResult, StackError> {
match &mut self.callbacks.connection_state.scan_results {
None => {
self.manager
.send_get_scan_result(index)
.map_err(StackError::WincWifiFail)?;
self.callbacks.connection_state.scan_results = Some(None);
}
Some(result) => {
if let Some(result) = result.take() {
self.callbacks.connection_state.scan_results = None;
return Ok(result);
}
}
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn get_ip_settings(&mut self) -> nb::Result<IPConf, StackError> {
if let Some(ip_conf) = &self.callbacks.connection_state.ip_conf {
return Ok(ip_conf.clone());
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn get_current_rssi(&mut self) -> nb::Result<i8, StackError> {
match &mut self.callbacks.connection_state.rssi_level {
None => {
self.manager
.send_get_current_rssi()
.map_err(StackError::WincWifiFail)?;
self.callbacks.connection_state.rssi_level = Some(None);
}
Some(rssi) => {
if let Some(rssi) = rssi.take() {
self.callbacks.connection_state.rssi_level = None;
return Ok(rssi);
}
}
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn get_connection_info(
&mut self,
) -> nb::Result<crate::manager::ConnectionInfo, StackError> {
match &mut self.callbacks.connection_state.conn_info {
None => {
self.manager
.send_get_conn_info()
.map_err(StackError::WincWifiFail)?;
self.callbacks.connection_state.conn_info = Some(None);
}
Some(info) => {
if let Some(info) = info.take() {
self.callbacks.connection_state.conn_info = None;
return Ok(info);
}
}
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn get_firmware_version(&mut self) -> Result<FirmwareInfo, StackError> {
self.manager
.get_firmware_ver_full()
.map_err(StackError::WincWifiFail)
}
pub fn send_ping(
&mut self,
dest_ip: core::net::Ipv4Addr,
ttl: u8,
count: u16,
) -> nb::Result<PingResult, StackError> {
match &mut self.callbacks.connection_state.ping_result {
None => {
info!("sending ping request");
let marker = 42; self.manager
.send_ping_req(dest_ip, ttl, count, marker)
.map_err(StackError::WincWifiFail)?;
self.callbacks.connection_state.ping_result = Some(None);
}
Some(result) => {
if let Some(result) = result.take() {
self.callbacks.connection_state.ping_result = None;
return Ok(result);
}
}
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn disconnect_ap(&mut self) -> nb::Result<(), StackError> {
match &mut self.callbacks.state {
WifiModuleState::ConnectedToAp => {
self.operation_countdown = AP_DISCONNECT_TIMEOUT_MILLISECONDS;
self.callbacks.state = WifiModuleState::Disconnecting;
self.manager
.send_disconnect()
.map_err(|x| nb::Error::Other(StackError::WincWifiFail(x)))?;
Err(nb::Error::WouldBlock)
}
WifiModuleState::Disconnecting => {
self.delay_us(self.poll_loop_delay_us); self.dispatch_events()?;
self.operation_countdown -= 1;
if self.operation_countdown == 0 {
return Err(nb::Error::Other(StackError::GeneralTimeout));
}
Err(nb::Error::WouldBlock)
}
_ => {
info!("disconnect_ap: got disconnected from AP");
Ok(())
}
}
}
pub fn provisioning_mode(
&mut self,
ap: &AccessPoint,
hostname: &HostName,
http_redirect: bool,
timeout: u32,
) -> nb::Result<ProvisioningInfo, StackError> {
match &mut self.callbacks.state {
WifiModuleState::Unconnected | WifiModuleState::ConnectedToAp => {
let auth = <Credentials as Into<AuthType>>::into(ap.key);
if auth == AuthType::S802_1X {
error!("Enterprise Security in provisioning mode is not supported");
return Err(nb::Error::Other(StackError::InvalidParameters));
}
self.manager
.send_start_provisioning(ap, hostname, http_redirect)
.map_err(|x| nb::Error::Other(StackError::WincWifiFail(x)))?;
self.callbacks.state = WifiModuleState::Provisioning;
self.callbacks.provisioning_info = None;
}
WifiModuleState::Provisioning => match &mut self.callbacks.provisioning_info {
None => {
self.operation_countdown = timeout * PROVISIONING_TIMEOUT;
self.callbacks.provisioning_info = Some(None);
}
Some(result) => {
if let Some(info) = result.take() {
if info.status {
return Ok(info);
}
return Err(nb::Error::Other(StackError::WincWifiFail(Error::Failed)));
} else {
self.delay_us(self.poll_loop_delay_us);
self.operation_countdown -= 1;
if self.operation_countdown == 0 {
return Err(nb::Error::Other(StackError::GeneralTimeout));
}
}
}
},
_ => {
return Err(nb::Error::Other(StackError::InvalidState));
}
}
self.dispatch_events()?;
Err(nb::Error::WouldBlock)
}
pub fn stop_provisioning_mode(&mut self) -> Result<(), StackError> {
if self.callbacks.state == WifiModuleState::Provisioning {
self.manager
.send_stop_provisioning()
.map_err(StackError::WincWifiFail)?;
} else {
return Err(StackError::InvalidState);
}
self.callbacks.state = WifiModuleState::Unconnected;
Ok(())
}
}
#[cfg(test)]
mod tests {
use core::net::Ipv4Addr;
use super::*;
use crate::client::{test_shared::*, SocketCallbacks};
use crate::errors::Error;
use crate::manager::Ssid;
use crate::manager::{EventListener, PingError, WifiConnError, WifiConnState};
use crate::{ConnectionInfo, Credentials, S8Password, S8Username, WifiChannel, WpaKey};
#[cfg(feature = "wep")]
use crate::{WepKey, WepKeyIndex};
#[test]
fn test_heartbeat() {
assert_eq!(make_test_client().heartbeat(), Ok(()));
}
#[test]
fn test_start_wifi_module() {
let mut client = make_test_client();
let result = nb::block!(client.start_wifi_module());
assert_eq!(
result,
Err(StackError::WincWifiFail(Error::BootRomStart).into())
);
}
#[test]
fn test_connect_to_saved_ap_invalid_state() {
let mut client = make_test_client();
let result = nb::block!(client.connect_to_saved_ap());
assert_eq!(result, Err(StackError::InvalidState));
}
#[test]
fn test_connect_to_saved_ap_timeout() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.connect_to_saved_ap());
assert_eq!(result, Err(StackError::GeneralTimeout));
}
#[test]
fn test_connect_to_saved_ap_success() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_connstate_changed(WifiConnState::Connected, WifiConnError::Unhandled);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.connect_to_saved_ap());
assert_eq!(result, Ok(()));
}
#[test]
fn test_connect_to_saved_ap_invalid_credentials() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_connstate_changed(WifiConnState::Disconnected, WifiConnError::AuthFail);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.connect_to_saved_ap());
assert_eq!(
result,
Err(StackError::ApJoinFailed(WifiConnError::AuthFail))
);
}
#[test]
fn test_connect_to_ap_success() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_connstate_changed(WifiConnState::Connected, WifiConnError::Unhandled);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.connect_to_ap("test", "test", false));
assert_eq!(result, Ok(()));
}
#[test]
fn test_scan_ok() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_scan_done(5, WifiConnError::Unhandled);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.scan());
assert_eq!(result, Ok(5));
}
#[test]
fn test_get_scan_result_ok() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_scan_result(ScanResult {
index: 0,
rssi: 0,
auth: AuthType::Open,
channel: 0,
bssid: [0; 6],
ssid: Ssid::from("test").unwrap(),
});
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.get_scan_result(0));
assert_eq!(result.unwrap().ssid, Ssid::from("test").unwrap());
}
#[test]
fn test_get_current_rssi_ok() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_rssi(0);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.get_current_rssi());
assert_eq!(result, Ok(0));
}
#[test]
fn test_get_connection_info_ok() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_connection_info(ConnectionInfo {
ssid: Ssid::from("test").unwrap(),
auth: AuthType::Open,
ip: Ipv4Addr::new(192, 168, 1, 1),
mac: [0; 6],
rssi: 0,
});
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.get_connection_info());
assert_eq!(result.unwrap().ssid, Ssid::from("test").unwrap());
}
#[test]
fn test_get_firmware_version_ok() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let result = client.get_firmware_version();
assert_eq!(result.unwrap().chip_id, 0);
}
#[test]
fn test_send_ping_ok() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_ping(
Ipv4Addr::new(192, 168, 1, 1),
0,
42,
0,
0,
PingError::Unhandled,
);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.send_ping(Ipv4Addr::new(192, 168, 1, 1), 64, 1));
assert!(result.is_ok());
assert_eq!(result.unwrap().rtt, 42);
}
#[test]
fn test_disconnect_success() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::ConnectedToAp;
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_connstate_changed(WifiConnState::Disconnected, WifiConnError::Unhandled);
};
client.debug_callback = Some(&mut my_debug);
let result = nb::block!(client.disconnect_ap());
assert!(result.is_ok());
}
#[test]
fn test_disconnect_timeout() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::ConnectedToAp;
let result = nb::block!(client.disconnect_ap());
assert_eq!(result.err(), Some(StackError::GeneralTimeout));
}
#[test]
fn test_disconnect_while_not_connected() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Starting;
let result = nb::block!(client.disconnect_ap());
assert!(result.is_ok());
}
#[test]
fn test_provisioning_mode_open_success() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let ap = AccessPoint::open(&ap_ssid);
let hostname = HostName::from("admin").unwrap();
let test_ssid = Ssid::from("test_ssid").unwrap();
let test_key = WpaKey::new();
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_provisioning(test_ssid, test_key, AuthType::Open, true);
};
client.debug_callback = Some(&mut my_debug);
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1));
assert!(result.is_ok());
if let Ok(info) = result {
assert_eq!(info.key, Credentials::Open);
assert_eq!(info.ssid, test_ssid);
} else {
assert!(false);
}
}
#[test]
fn test_provisioning_mode_wpa_success() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let ap_key = WpaKey::from("wpa_key").unwrap();
let ap = AccessPoint::wpa(&ap_ssid, &ap_key);
let hostname = HostName::from("admin").unwrap();
let test_ssid = Ssid::from("test_ssid").unwrap();
let test_key = WpaKey::from("test_key").unwrap();
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_provisioning(test_ssid, test_key, AuthType::WpaPSK, true);
};
client.debug_callback = Some(&mut my_debug);
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1));
assert!(result.is_ok());
if let Ok(info) = result {
assert_eq!(info.key, Credentials::WpaPSK(test_key));
assert_eq!(info.ssid, test_ssid);
} else {
assert!(false);
}
}
#[cfg(feature = "wep")]
#[test]
fn test_provisioning_mode_wep_success() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let ap_key = WepKey::from("wep_key").unwrap();
let wep_key_index = WepKeyIndex::Key1;
let ap = AccessPoint::wep(&ap_ssid, &ap_key, wep_key_index);
let hostname = HostName::from("admin").unwrap();
let test_ssid = Ssid::from("test_ssid").unwrap();
let test_key = WpaKey::from("test_wep_key").unwrap();
let test_wep_key = WepKey::from("test_wep_key").unwrap();
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_provisioning(test_ssid, test_key, AuthType::WEP, true);
};
client.debug_callback = Some(&mut my_debug);
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1));
assert!(result.is_ok());
if let Ok(info) = result {
assert_eq!(info.key, Credentials::Wep(test_wep_key, wep_key_index));
assert_eq!(info.ssid, test_ssid);
} else {
assert!(false);
}
}
#[test]
fn test_provisioning_mode_enterprise_fail() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let s8_username = S8Username::from("username").unwrap();
let s8_password = S8Password::from("password").unwrap();
let ap_key = Credentials::S802_1X(s8_username, s8_password);
let ap = AccessPoint {
ssid: &ap_ssid,
key: ap_key,
channel: WifiChannel::Channel1,
ssid_hidden: false,
ip: Ipv4Addr::new(192, 168, 1, 1),
};
let hostname = HostName::from("admin").unwrap();
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1));
assert!(result.is_err());
if let Err(error) = result {
assert_eq!(error, StackError::InvalidParameters);
} else {
assert!(false);
}
}
#[test]
fn test_provisioning_invalid_state() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let ap = AccessPoint::open(&ap_ssid);
let hostname = HostName::from("admin").unwrap();
client.callbacks.state = WifiModuleState::ConnectingToAp;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1));
assert!(result.is_err());
if let Err(err) = result {
assert_eq!(err, StackError::InvalidState);
} else {
assert!(false);
}
}
#[test]
fn test_provisioning_timeout() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let ap = AccessPoint::open(&ap_ssid);
let hostname = HostName::from("admin").unwrap();
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1500));
assert!(result.is_err());
if let Err(err) = result {
assert_eq!(err, StackError::GeneralTimeout);
} else {
assert!(false);
}
}
#[test]
fn test_provisioning_failed() {
let mut client = make_test_client();
let ap_ssid = Ssid::from("ssid").unwrap();
let ap = AccessPoint::open(&ap_ssid);
let hostname = HostName::from("admin").unwrap();
let test_ssid = Ssid::from("test_ssid").unwrap();
let test_key = WpaKey::new();
let mut my_debug = |callbacks: &mut SocketCallbacks| {
callbacks.on_provisioning(test_ssid, test_key, AuthType::Open, false);
};
client.debug_callback = Some(&mut my_debug);
client.callbacks.state = WifiModuleState::Unconnected;
let result = nb::block!(client.provisioning_mode(&ap, &hostname, false, 1));
assert!(result.is_err());
if let Err(error) = result {
assert_eq!(error, StackError::WincWifiFail(Error::Failed));
} else {
assert!(false);
}
}
#[test]
fn test_stop_provisioning_success() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Provisioning;
let result = client.stop_provisioning_mode();
assert!(result.is_ok());
}
#[test]
fn test_stop_provisioning_state_error() {
let mut client = make_test_client();
client.callbacks.state = WifiModuleState::Unconnected;
let result = client.stop_provisioning_mode();
assert!(result.is_err());
}
}