extern crate alloc;
use alloc::{sync::Arc, vec::Vec};
use crate::fdrv::{
core::bus::WifiBus,
protocol::{
collect_scan_results, lmac_msg::*, send_key_add_req, send_key_del_req,
send_scanu_start_req, send_set_control_port_req, send_sm_connect_req,
send_sm_disconnect_req, wait_for_indication,
},
};
pub fn scan(
bus: &Arc<WifiBus>,
vif_idx: u8,
ssid: Option<&[u8]>,
timeout_ms: u64,
) -> Result<Vec<ScanResult>, CmdError> {
{
let mut queue = bus.tx.ind_queue.lock();
queue.clear();
}
let _cfm = send_scanu_start_req(bus, vif_idx, ssid, timeout_ms)?;
let results = collect_scan_results(bus, timeout_ms);
log::debug!("[wifi_mgr] scan complete, {} APs found", results.len());
Ok(results)
}
pub fn find_ap_by_ssid<'a>(
results: &'a [ScanResult],
target_ssid: &[u8],
) -> Option<&'a ScanResult> {
results.iter().find(|ap| {
let len = ap.ssid_len as usize;
len == target_ssid.len() && ap.ssid[..len] == *target_ssid
})
}
pub fn connect(
bus: &Arc<WifiBus>,
vif_idx: u8,
ssid: &[u8],
bssid: &[u8; 6],
channel_freq: u16,
wpa2_ie: &[u8],
timeout_ms: u64,
) -> Result<ConnectResult, CmdError> {
log::debug!(
"[wifi_mgr] connect: ssid_len={}, bssid={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}, freq={}",
ssid.len(),
bssid[0],
bssid[1],
bssid[2],
bssid[3],
bssid[4],
bssid[5],
channel_freq
);
{
let mut queue = bus.tx.ind_queue.lock();
queue.clear();
}
let flags: u32 = if wpa2_ie.is_empty() {
0
} else {
WPA_WPA2_IN_USE | CONTROL_PORT_HOST | CONTROL_PORT_NO_ENC
};
let cfm = send_sm_connect_req(
bus,
vif_idx,
ssid,
bssid,
channel_freq,
flags,
WLAN_AUTH_OPEN, wpa2_ie,
5000,
)?;
if !cfm.is_empty() && cfm[0] != 0 {
log::error!("[wifi_mgr] SM_CONNECT_CFM status={} (rejected)", cfm[0]);
return Err(CmdError::FirmwareError);
}
log::debug!("[wifi_mgr] SM_CONNECT_CFM OK, waiting for SM_CONNECT_IND...");
let ind = wait_for_indication(
bus,
SM_CONNECT_IND,
&[SM_DISCONNECT_IND, SM_EXTERNAL_AUTH_REQUIRED_IND],
timeout_ms,
)?;
let result = parse_connect_ind(&ind)?;
if result.status_code != 0 {
log::error!(
"[wifi_mgr] SM_CONNECT_IND: connection failed, status_code={}",
result.status_code
);
return Err(CmdError::FirmwareError);
}
log::debug!(
"[wifi_mgr] SM_CONNECT_IND: ap_idx={}, ch_idx={}, aid={}",
result.ap_idx,
result.ch_idx,
result.aid
);
if wpa2_ie.is_empty() {
send_set_control_port_req(bus, result.ap_idx, true, 5000)?;
log::debug!(
"[wifi_mgr] open network: control port opened for sta_idx={}",
result.ap_idx
);
} else {
log::debug!("[wifi_mgr] WPA2: waiting for handshake before opening control port");
}
Ok(result)
}
fn parse_connect_ind(param: &[u8]) -> Result<ConnectResult, CmdError> {
if param.len() < 20 {
log::error!("[wifi_mgr] SM_CONNECT_IND too short: {} bytes", param.len());
return Err(CmdError::InvalidResponse);
}
let status_code = u16::from_le_bytes([param[0], param[1]]);
let mut bssid = [0u8; 6];
bssid.copy_from_slice(¶m[2..8]);
let vif_idx = param[9];
let ap_idx = param[10];
let ch_idx = param[11];
let qos = param[12] != 0;
let assoc_req_ie_len = u16::from_le_bytes([param[14], param[15]]) as usize;
let assoc_rsp_ie_len = u16::from_le_bytes([param[16], param[17]]) as usize;
let aid = if param.len() >= 822 {
u16::from_le_bytes([param[820], param[821]])
} else {
log::warn!(
"[wifi_mgr] SM_CONNECT_IND too short for aid field ({} bytes), defaulting to 0",
param.len()
);
0
};
log::debug!(
"[wifi_mgr] SM_CONNECT_IND param_len={}, status={}, \
bssid={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
param.len(),
status_code,
bssid[0],
bssid[1],
bssid[2],
bssid[3],
bssid[4],
bssid[5]
);
const IE_BUF_OFFSET: usize = 20;
let assoc_req_ies = if param.len() >= IE_BUF_OFFSET + assoc_req_ie_len && assoc_req_ie_len > 0 {
let ies = param[IE_BUF_OFFSET..IE_BUF_OFFSET + assoc_req_ie_len].to_vec();
let mut offset = 0;
while offset + 2 <= ies.len() {
let tag = ies[offset];
let len = ies[offset + 1] as usize;
if offset + 2 + len > ies.len() {
break;
}
if tag == 0x30 {
let rsn = &ies[offset..offset + 2 + len];
log::debug!(
"[wifi_mgr] >>> AssocReq RSN IE (firmware sent): {:02x?}",
rsn
);
break;
}
offset += 2 + len;
}
ies
} else {
log::warn!(
"[wifi_mgr] Cannot extract AssocReq IEs: param_len={}, ie_buf_offset={}, \
assoc_req_ie_len={}, assoc_rsp_ie_len={}",
param.len(),
IE_BUF_OFFSET,
assoc_req_ie_len,
assoc_rsp_ie_len
);
Vec::new()
};
Ok(ConnectResult {
status_code,
bssid,
vif_idx,
ap_idx,
ch_idx,
qos,
aid,
assoc_req_ies,
})
}
pub fn install_pairwise_key(
bus: &Arc<WifiBus>,
vif_idx: u8,
sta_idx: u8,
cipher: u8,
key: &[u8],
key_idx: u8,
) -> Result<u8, CmdError> {
log::debug!(
"[wifi_mgr] install pairwise key: vif={}, sta={}, cipher={}, key_len={}",
vif_idx,
sta_idx,
cipher,
key.len()
);
let hw_key_idx = send_key_add_req(bus, vif_idx, sta_idx, true, key, key_idx, cipher, 5000)?;
log::debug!(
"[wifi_mgr] pairwise key installed, hw_key_idx={}",
hw_key_idx
);
Ok(hw_key_idx)
}
pub fn install_group_key(
bus: &Arc<WifiBus>,
vif_idx: u8,
sta_idx: u8,
cipher: u8,
key: &[u8],
key_idx: u8,
) -> Result<u8, CmdError> {
log::debug!(
"[wifi_mgr] install group key: vif={}, sta={}, cipher={}, key_idx={}, key_len={}",
vif_idx,
sta_idx,
cipher,
key_idx,
key.len()
);
let hw_key_idx = send_key_add_req(bus, vif_idx, sta_idx, false, key, key_idx, cipher, 5000)?;
log::debug!("[wifi_mgr] group key installed, hw_key_idx={}", hw_key_idx);
Ok(hw_key_idx)
}
pub fn delete_key(bus: &Arc<WifiBus>, hw_key_idx: u8) -> Result<(), CmdError> {
log::debug!("[wifi_mgr] delete key: hw_key_idx={}", hw_key_idx);
send_key_del_req(bus, hw_key_idx, 5000)?;
Ok(())
}
pub fn disconnect(bus: &Arc<WifiBus>, vif_idx: u8, reason_code: u16) -> Result<(), CmdError> {
log::debug!(
"[wifi_mgr] disconnect: vif_idx={}, reason_code={}",
vif_idx,
reason_code
);
let cfm = send_sm_disconnect_req(bus, vif_idx, reason_code, 5000)?;
if !cfm.is_empty() && cfm[0] != 0 {
log::warn!("[wifi_mgr] SM_DISCONNECT_CFM status={}", cfm[0]);
}
match wait_for_indication(bus, SM_DISCONNECT_IND, &[], 5000) {
Ok(ind) => {
if ind.len() >= 4 {
let reason = u16::from_le_bytes([ind[0], ind[1]]);
let vif = ind[2];
let ft_over_ds = ind[3];
log::debug!(
"[wifi_mgr] SM_DISCONNECT_IND: reason={}, vif={}, ft_over_ds={}",
reason,
vif,
ft_over_ds
);
}
}
Err(CmdError::Timeout) => {
log::warn!(
"[wifi_mgr] SM_DISCONNECT_IND timeout (disconnect may still have succeeded)"
);
}
Err(e) => {
log::error!("[wifi_mgr] SM_DISCONNECT_IND error: {:?}", e);
return Err(e);
}
}
log::debug!("[wifi_mgr] disconnected");
Ok(())
}
pub fn handle_disconnect_ind(param: &[u8]) {
if param.len() >= 4 {
let reason = u16::from_le_bytes([param[0], param[1]]);
let vif_idx = param[2];
log::warn!(
"[wifi_mgr] passive disconnect: vif={}, reason={}",
vif_idx,
reason
);
} else {
log::warn!(
"[wifi_mgr] passive disconnect (short param: {} bytes)",
param.len()
);
}
}
pub fn build_wpa2_rsn_ie() -> Vec<u8> {
let mut ie = Vec::with_capacity(22);
ie.push(0x30); ie.push(20); ie.extend_from_slice(&1u16.to_le_bytes());
ie.extend_from_slice(&[0x00, 0x0F, 0xAC, 0x04]);
ie.extend_from_slice(&1u16.to_le_bytes());
ie.extend_from_slice(&[0x00, 0x0F, 0xAC, 0x04]);
ie.extend_from_slice(&1u16.to_le_bytes());
ie.extend_from_slice(&[0x00, 0x0F, 0xAC, 0x02]);
ie.extend_from_slice(&0u16.to_le_bytes());
ie
}
pub fn build_wpa2_rsn_ie_from_ap(ap_rsn_ie: &[u8]) -> Vec<u8> {
let group_cipher = if ap_rsn_ie.len() >= 8 {
&ap_rsn_ie[4..8]
} else {
&[0x00, 0x0F, 0xAC, 0x02]
};
let rsn_cap = [0x00, 0x00];
let mut ie = Vec::with_capacity(22);
ie.push(0x30); ie.push(20); ie.extend_from_slice(&1u16.to_le_bytes());
ie.extend_from_slice(group_cipher);
ie.extend_from_slice(&1u16.to_le_bytes());
ie.extend_from_slice(&[0x00, 0x0F, 0xAC, 0x04]);
ie.extend_from_slice(&1u16.to_le_bytes());
ie.extend_from_slice(&[0x00, 0x0F, 0xAC, 0x02]);
ie.extend_from_slice(&rsn_cap);
ie
}