use alloc::{format, string::String, sync::Arc, vec::Vec};
use core::fmt;
use crate::{
common::ChipVariant,
fdrv::{
consts::AP_MODE_FILTER_DEFAULT,
core::bus::WifiBus,
crypto::wpa2::*,
protocol::{
lmac_msg::*, send_apm_stop_req, send_eapol_data_frame, send_get_mac_addr_req,
send_me_chan_config_req, send_me_config_req, send_me_set_ps_mode_req,
send_mm_add_if_req, send_mm_add_if_req_typed, send_mm_remove_if_req,
send_mm_set_filter_req, send_mm_start_req, send_reset_req, send_rf_calib_req,
send_set_control_port_req, start_open_ap, wait_for_eapol,
},
wifi::manager::{self, build_wpa2_rsn_ie_from_ap, disconnect},
},
};
#[derive(Clone, Debug)]
pub struct WifiConfig {
pub ssid: Vec<u8>,
pub password: Option<Vec<u8>>,
pub bssid: Option<[u8; 6]>,
pub auth_type: WifiAuthType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WifiAuthType {
Open,
Wpa2Psk,
Wpa3Psk,
}
impl WifiConfig {
pub fn open(ssid: &str) -> Self {
Self {
ssid: ssid.as_bytes().to_vec(),
password: None,
bssid: None,
auth_type: WifiAuthType::Open,
}
}
pub fn wpa2_psk(ssid: &str, password: &str) -> Self {
Self {
ssid: ssid.as_bytes().to_vec(),
password: Some(password.as_bytes().to_vec()),
bssid: None,
auth_type: WifiAuthType::Wpa2Psk,
}
}
pub fn with_bssid(mut self, bssid: [u8; 6]) -> Self {
self.bssid = Some(bssid);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WifiError {
NotInitialized,
ScanFailed,
NetworkNotFound,
AuthenticationFailed,
ConnectionTimeout,
InvalidPassword,
NetworkUnavailable,
AlreadyConnected,
NotConnected,
OperationFailed(String),
}
impl fmt::Display for WifiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WifiError::NotInitialized => write!(f, "Driver not initialized"),
WifiError::ScanFailed => write!(f, "Scan failed"),
WifiError::NetworkNotFound => write!(f, "Network not found"),
WifiError::AuthenticationFailed => write!(f, "Authentication failed"),
WifiError::ConnectionTimeout => write!(f, "Connection timeout"),
WifiError::InvalidPassword => write!(f, "Invalid password"),
WifiError::NetworkUnavailable => write!(f, "Network unavailable"),
WifiError::AlreadyConnected => write!(f, "Already connected"),
WifiError::NotConnected => write!(f, "Not connected"),
WifiError::OperationFailed(msg) => write!(f, "Operation failed: {}", msg),
}
}
}
impl core::error::Error for WifiError {}
impl From<CmdError> for WifiError {
fn from(e: CmdError) -> Self {
match e {
CmdError::Timeout => WifiError::ConnectionTimeout,
CmdError::FirmwareError => WifiError::AuthenticationFailed,
_ => WifiError::OperationFailed(format!("{:?}", e)),
}
}
}
#[derive(Clone, Debug)]
pub struct WifiNetwork {
pub ssid: Vec<u8>,
pub ssid_len: u8,
pub bssid: [u8; 6],
pub rssi: i8,
pub channel_freq: u16,
pub encryption: WifiEncryption,
pub has_rsn: bool,
pub rsn_ie: Vec<u8>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WifiEncryption {
None,
Wep,
Wpa,
Wpa2,
Wpa3,
Unknown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConnectionStatus {
Disconnected,
Connecting,
Connected,
Failed,
}
pub struct WifiClient {
bus: Arc<WifiBus>,
vif_idx: u8,
sta_mac: Option<[u8; 6]>,
}
impl WifiClient {
pub fn new(bus: Arc<WifiBus>) -> Self {
Self {
bus,
vif_idx: 0,
sta_mac: None,
}
}
pub fn with_vif_idx(mut self, vif_idx: u8) -> Self {
self.vif_idx = vif_idx;
self
}
pub fn teardown_vif(&mut self, timeout_ms: u64) -> Result<(), WifiError> {
let vif_idx = self
.bus
.conn
.vif_idx
.load(core::sync::atomic::Ordering::Acquire);
if vif_idx == 0xFF {
return Ok(());
}
if self.get_status() == ConnectionStatus::Connected
&& let Err(e) = disconnect(&self.bus, vif_idx, 3)
{
log::warn!(
"[WifiClient] teardown: best-effort disconnect failed: {:?}",
e
);
}
send_mm_remove_if_req(&self.bus, vif_idx, timeout_ms).map_err(WifiError::from)?;
log::info!("[WifiClient] teardown: removed vif_idx={}", vif_idx);
self.bus
.conn
.set_status(crate::fdrv::core::STATUS_DISCONNECTED);
self.bus
.conn
.vif_idx
.store(0xFF, core::sync::atomic::Ordering::Release);
self.bus
.conn
.sta_idx
.store(0xFF, core::sync::atomic::Ordering::Release);
*self.bus.conn.ap_mac.lock() = None;
self.vif_idx = 0xFF;
Ok(())
}
pub fn lmac_configure(
&mut self,
chip: ChipVariant,
timeout_ms: u64,
) -> Result<[u8; 6], WifiError> {
self.teardown_vif(timeout_ms)?;
send_rf_calib_req(&self.bus, chip, timeout_ms).map_err(WifiError::from)?;
let mac = send_get_mac_addr_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
send_reset_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
send_me_config_req(&self.bus, chip, timeout_ms).map_err(WifiError::from)?;
send_me_chan_config_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
let vif_idx = send_mm_add_if_req(&self.bus, &mac, timeout_ms).map_err(WifiError::from)?;
self.vif_idx = vif_idx;
send_mm_start_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
send_mm_set_filter_req(&self.bus, 0x1502_868C, timeout_ms).map_err(WifiError::from)?;
self.sta_mac = Some(mac);
self.bus
.conn
.vif_idx
.store(vif_idx, core::sync::atomic::Ordering::Release);
log::debug!(
"[WifiClient] LMAC configured: mac={:02x?}, vif_idx={}",
mac,
vif_idx
);
Ok(mac)
}
pub fn start_ap_open(
&mut self,
chip: ChipVariant,
ssid: &[u8],
channel: u8,
timeout_ms: u64,
) -> Result<Vec<u8>, WifiError> {
log::info!("[WifiClient] === start_ap_open START ===");
self.teardown_vif(timeout_ms)?;
send_rf_calib_req(&self.bus, chip, timeout_ms).map_err(WifiError::from)?;
let mac = send_get_mac_addr_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
send_reset_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
send_me_config_req(&self.bus, chip, timeout_ms).map_err(WifiError::from)?;
send_me_chan_config_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
let vif_idx =
send_mm_add_if_req_typed(&self.bus, &mac, MM_AP, timeout_ms).map_err(|e| {
log::error!("[WifiClient] MM_ADD_IF(AP) failed: {:?}", e);
WifiError::from(e)
})?;
self.vif_idx = vif_idx;
log::info!(
"[WifiClient] AP interface added: vif_idx={}, mac={:02x?}",
vif_idx,
mac
);
send_mm_start_req(&self.bus, timeout_ms).map_err(WifiError::from)?;
send_mm_set_filter_req(&self.bus, AP_MODE_FILTER_DEFAULT, timeout_ms)
.map_err(WifiError::from)?;
self.sta_mac = Some(mac);
self.bus
.conn
.vif_idx
.store(vif_idx, core::sync::atomic::Ordering::Release);
self.bus.conn.sta_mac.lock().replace(mac);
log::info!("[WifiClient] Starting AP (beacon + APM_START)...");
let cfm =
start_open_ap(&self.bus, vif_idx, &mac, ssid, channel, timeout_ms).map_err(|e| {
log::error!("[WifiClient] start_open_ap failed: {:?}", e);
WifiError::from(e)
})?;
let status = cfm.first().copied().unwrap_or(0xFF);
log::info!(
"[WifiClient] === APM_START_CFM received! status={}, cfm={:02x?} ===",
status,
&cfm[..cfm.len().min(8)]
);
if status == 0 {
log::info!("[WifiClient] *** AP STARTED — SSID broadcasting ***");
} else {
log::warn!(
"[WifiClient] APM_START_CFM status != 0 ({}), AP may not be up",
status
);
}
Ok(cfm)
}
pub fn stop_ap(&self, timeout_ms: u64) -> Result<(), WifiError> {
send_apm_stop_req(&self.bus, self.vif_idx, timeout_ms).map_err(WifiError::from)?;
log::info!("[WifiClient] AP stopped (vif_idx={})", self.vif_idx);
Ok(())
}
pub fn scan(
&self,
ssid: Option<&[u8]>,
timeout_ms: u64,
) -> Result<Vec<WifiNetwork>, WifiError> {
log::debug!("[WifiClient] Starting scan...");
let results = manager::scan(&self.bus, self.vif_idx, ssid, timeout_ms).map_err(|e| {
log::error!("[WifiClient] Scan failed: {:?}", e);
WifiError::ScanFailed
})?;
let networks: Vec<WifiNetwork> = results
.into_iter()
.map(|r| WifiNetwork {
ssid: r.ssid.to_vec(),
ssid_len: r.ssid_len,
bssid: r.bssid,
rssi: r.rssi,
channel_freq: r.center_freq,
encryption: if r.rsn_ie.is_empty() {
WifiEncryption::None
} else {
WifiEncryption::Wpa2
},
has_rsn: !r.rsn_ie.is_empty(),
rsn_ie: r.rsn_ie,
})
.collect();
log::debug!(
"[WifiClient] Scan complete: {} networks found",
networks.len()
);
Ok(networks)
}
pub fn find_network(&self, ssid: &[u8], timeout_ms: u64) -> Result<WifiNetwork, WifiError> {
let networks = self.scan(Some(ssid), timeout_ms)?;
networks
.into_iter()
.find(|n| n.ssid[..n.ssid_len as usize] == *ssid)
.ok_or(WifiError::NetworkNotFound)
}
pub fn connect(&self, config: &WifiConfig, timeout_ms: u64) -> Result<(), WifiError> {
log::info!(
"[WifiClient] Connecting to SSID: {:?}, auth: {:?}",
core::str::from_utf8(&config.ssid).unwrap_or("<invalid>"),
config.auth_type
);
if self.get_status() == ConnectionStatus::Connected {
return Err(WifiError::AlreadyConnected);
}
let network = self.find_network(&config.ssid, 10000)?;
self.connect_to(&network, config, timeout_ms)
}
pub fn connect_to(
&self,
network: &WifiNetwork,
config: &WifiConfig,
timeout_ms: u64,
) -> Result<(), WifiError> {
self.bus
.conn
.set_status(crate::fdrv::core::STATUS_CONNECTING);
let result = self.connect_to_inner(network, config, timeout_ms);
match &result {
Ok(()) => {
self.bus
.conn
.set_status(crate::fdrv::core::STATUS_CONNECTED);
self.bus.conn.ap_mac.lock().replace(network.bssid);
}
Err(_) => {
self.bus.conn.set_status(crate::fdrv::core::STATUS_FAILED);
}
}
result
}
fn connect_to_inner(
&self,
network: &WifiNetwork,
config: &WifiConfig,
timeout_ms: u64,
) -> Result<(), WifiError> {
let wpa2_ie = if config.auth_type == WifiAuthType::Wpa2Psk {
if !network.has_rsn {
log::warn!(
"[WifiClient] Target network doesn't have RSN IE, using default WPA2 IE"
);
}
build_wpa2_rsn_ie_from_ap(&network.rsn_ie)
} else {
Vec::new()
};
let connect_result = manager::connect(
&self.bus,
self.vif_idx,
&config.ssid,
&network.bssid,
network.channel_freq,
&wpa2_ie,
timeout_ms,
)
.map_err(WifiError::from)?;
log::debug!(
"[WifiClient] Connection established: ap_idx={}",
connect_result.ap_idx
);
self.bus
.conn
.sta_idx
.store(connect_result.ap_idx, core::sync::atomic::Ordering::Release);
if config.auth_type == WifiAuthType::Wpa2Psk
&& let Some(ref password) = config.password
{
self.perform_wpa2_handshake(
connect_result.ap_idx,
&network.bssid,
&config.ssid,
&wpa2_ie,
password,
timeout_ms,
)?;
}
if let Err(e) = send_me_set_ps_mode_req(&self.bus, MM_PS_MODE_OFF, 3000) {
log::warn!("[WifiClient] Failed to disable PS mode: {:?}", e);
}
Ok(())
}
fn perform_wpa2_handshake(
&self,
sta_idx: u8,
bssid: &[u8; 6],
ssid: &[u8],
rsn_ie: &[u8],
password: &[u8],
timeout_ms: u64,
) -> Result<(), WifiError> {
log::debug!("[WifiClient] Starting WPA2 handshake...");
let own_mac = self.sta_mac.ok_or_else(|| {
WifiError::OperationFailed("MAC address not set, call lmac_configure() first".into())
})?;
let mut handshake = Wpa2Handshake::new(password, ssid, bssid, &own_mac, rsn_ie);
loop {
let eapol =
wait_for_eapol(&self.bus, timeout_ms).map_err(|_| WifiError::ConnectionTimeout)?;
log::debug!(
"[wpa2] wait_for_eapol got {} bytes, vif={} sta_idx={}",
eapol.len(),
self.vif_idx,
sta_idx
);
match handshake.process_eapol(&eapol) {
Ok(HandshakeAction::SendM2(m2)) => {
log::debug!("[wpa2] sending M2 ({} bytes) -> {:02x?}", m2.len(), bssid);
match send_eapol_data_frame(
&self.bus,
bssid,
&own_mac,
&m2,
self.vif_idx,
sta_idx,
) {
Ok(()) => log::debug!("[wpa2] M2 send_eapol_data_frame returned Ok"),
Err(e) => {
log::error!("[wpa2] M2 send failed: {:?}", e);
return Err(WifiError::OperationFailed(format!(
"Send M2 failed: {:?}",
e
)));
}
}
}
Ok(HandshakeAction::Completed(result)) => {
manager::install_pairwise_key(
&self.bus,
self.vif_idx,
sta_idx,
MAC_CIPHER_CCMP,
&result.tk,
0,
)
.map_err(|e| {
WifiError::OperationFailed(format!("Install PTK failed: {:?}", e))
})?;
manager::install_group_key(
&self.bus,
self.vif_idx,
0xFF,
MAC_CIPHER_CCMP,
&result.gtk,
result.gtk_key_idx,
)
.map_err(|e| {
WifiError::OperationFailed(format!("Install GTK failed: {:?}", e))
})?;
send_eapol_data_frame(
&self.bus,
bssid,
&own_mac,
&result.m4_frame,
self.vif_idx,
sta_idx,
)
.map_err(|e| WifiError::OperationFailed(format!("Send M4 failed: {:?}", e)))?;
send_set_control_port_req(&self.bus, sta_idx, true, 5000).map_err(|e| {
WifiError::OperationFailed(format!("Open control port failed: {:?}", e))
})?;
log::info!("[WifiClient] WPA2 handshake completed");
return Ok(());
}
Err(WpaError::MicMismatch) => return Err(WifiError::InvalidPassword),
Err(e) => {
return Err(WifiError::OperationFailed(format!(
"Handshake error: {:?}",
e
)));
}
}
}
}
pub fn get_status(&self) -> ConnectionStatus {
match self.bus.conn.get_status() {
crate::fdrv::core::STATUS_DISCONNECTED => ConnectionStatus::Disconnected,
crate::fdrv::core::STATUS_CONNECTING => ConnectionStatus::Connecting,
crate::fdrv::core::STATUS_CONNECTED => ConnectionStatus::Connected,
crate::fdrv::core::STATUS_FAILED => ConnectionStatus::Failed,
_ => ConnectionStatus::Disconnected,
}
}
pub fn get_mac_address(&self) -> Option<[u8; 6]> {
self.sta_mac
}
pub fn wait_for_connection(&self, timeout_ms: u64) -> Result<(), WifiError> {
let start = crate::runtime::runtime().now_nanos();
let timeout_ns = timeout_ms * 1_000_000;
loop {
if self.get_status() == ConnectionStatus::Connected {
return Ok(());
}
let elapsed = crate::runtime::runtime().now_nanos() - start;
if elapsed > timeout_ns {
return Err(WifiError::ConnectionTimeout);
}
crate::runtime::runtime().yield_now();
}
}
pub fn disconnect(&self) -> Result<(), WifiError> {
log::debug!("[WifiClient] Disconnecting...");
if self.get_status() == ConnectionStatus::Disconnected {
return Err(WifiError::NotConnected);
}
disconnect(&self.bus, self.vif_idx, 3)
.map_err(|e| WifiError::OperationFailed(format!("Disconnect failed: {:?}", e)))?;
self.bus
.conn
.set_status(crate::fdrv::core::STATUS_DISCONNECTED);
*self.bus.conn.ap_mac.lock() = None;
log::debug!("[WifiClient] Disconnected");
Ok(())
}
pub fn get_current_ssid(&self) -> Option<Vec<u8>> {
if self.get_status() != ConnectionStatus::Connected {
return None;
}
None
}
pub fn get_rssi(&self) -> Option<i8> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wifi_config_open() {
let config = WifiConfig::open("TestNetwork");
assert_eq!(config.ssid, b"TestNetwork".to_vec());
assert_eq!(config.auth_type, WifiAuthType::Open);
assert!(config.password.is_none());
}
#[test]
fn test_wifi_config_wpa2() {
let config = WifiConfig::wpa2_psk("TestNetwork", "password123");
assert_eq!(config.ssid, b"TestNetwork".to_vec());
assert_eq!(config.auth_type, WifiAuthType::Wpa2Psk);
assert_eq!(config.password, Some(b"password123".to_vec()));
}
#[test]
fn test_wifi_config_with_bssid() {
let bssid = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let config = WifiConfig::open("TestNetwork").with_bssid(bssid);
assert_eq!(config.bssid, Some(bssid));
}
#[test]
fn test_wifi_error_display() {
let err = WifiError::NetworkNotFound;
assert_eq!(format!("{}", err), "Network not found");
}
}