use alloc::{string::String, vec::Vec};
use core::mem::MaybeUninit;
use ax_errno::{AxError, AxResult};
use spin::Mutex;
use starry_vm::{vm_read_slice, vm_write_slice};
pub const SIOCSIWCOMMIT: u32 = 0x8B00;
pub const SIOCSIWFREQ: u32 = 0x8B04;
pub const SIOCSIWMODE: u32 = 0x8B06;
pub const SIOCSIWESSID: u32 = 0x8B1A;
pub const SIOCSIWENCODEEXT: u32 = 0x8B34;
const IW_MODE_INFRA: u32 = 2; const IW_MODE_MASTER: u32 = 3;
const IWREQ_NAME_LEN: usize = 16;
const IWREQ_DATA_OFFSET: usize = 16;
const IW_ESSID_MAX_SIZE: usize = 32;
const MAX_PASSPHRASE: usize = 63;
const AP_SERVER_IP: [u8; 4] = [192, 168, 50, 1];
const AP_CLIENT_IP: [u8; 4] = [192, 168, 50, 2];
const AP_PREFIX_LEN: u8 = 24;
const AP_CHANNEL_DEFAULT: u8 = 6;
#[derive(Clone, Copy, PartialEq, Eq)]
enum StagedMode {
Station,
AccessPoint,
}
#[derive(Clone)]
struct Pending {
ifname: String,
mode: Option<StagedMode>,
ssid: Option<Vec<u8>>,
passphrase: Option<String>,
channel: Option<u8>,
}
impl Pending {
fn new(ifname: String) -> Self {
Self {
ifname,
mode: None,
ssid: None,
passphrase: None,
channel: None,
}
}
}
static PENDING: Mutex<Vec<Pending>> = Mutex::new(Vec::new());
fn with_pending<R>(ifname: &str, f: impl FnOnce(&mut Pending) -> R) -> R {
let mut table = PENDING.lock();
if let Some(idx) = table.iter().position(|p| p.ifname == ifname) {
f(&mut table[idx])
} else {
table.push(Pending::new(ifname.into()));
let last = table.len() - 1;
f(&mut table[last])
}
}
fn take_pending(ifname: &str) -> Option<Pending> {
let mut table = PENDING.lock();
table
.iter()
.position(|p| p.ifname == ifname)
.map(|idx| table.swap_remove(idx))
}
fn read_user_array<const N: usize>(ptr: *const u8) -> AxResult<[u8; N]> {
let mut buf = [MaybeUninit::<u8>::uninit(); N];
vm_read_slice(ptr, &mut buf)?;
Ok(buf.map(|v| unsafe { v.assume_init() }))
}
fn read_ifname(arg: usize) -> AxResult<String> {
let buf = read_user_array::<IWREQ_NAME_LEN>(arg as *const u8)?;
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8(buf[..end].to_vec()).map_err(|_| AxError::InvalidInput)
}
fn read_iwreq_data(arg: usize) -> AxResult<[u8; 16]> {
read_user_array::<16>((arg + IWREQ_DATA_OFFSET) as *const u8)
}
fn read_iw_point(arg: usize, max: usize) -> AxResult<Vec<u8>> {
let data = read_iwreq_data(arg)?;
let ptr = usize::from_ne_bytes(
data[..core::mem::size_of::<usize>()]
.try_into()
.map_err(|_| AxError::InvalidInput)?,
);
let len = u16::from_ne_bytes([data[8], data[9]]) as usize;
if ptr == 0 || len == 0 {
return Ok(Vec::new());
}
if len > max {
return Err(AxError::InvalidInput);
}
let mut buf = alloc::vec![MaybeUninit::<u8>::uninit(); len];
vm_read_slice(ptr as *const u8, &mut buf)?;
Ok(buf
.into_iter()
.map(|v| unsafe { v.assume_init() })
.collect())
}
pub fn is_wext_ioctl(cmd: u32) -> bool {
matches!(
cmd,
SIOCSIWCOMMIT | SIOCSIWFREQ | SIOCSIWMODE | SIOCSIWESSID | SIOCSIWENCODEEXT
)
}
pub fn handle(cmd: u32, arg: usize) -> AxResult<usize> {
let ifname = read_ifname(arg)?;
match cmd {
SIOCSIWMODE => {
let data = read_iwreq_data(arg)?;
let mode = u32::from_ne_bytes([data[0], data[1], data[2], data[3]]);
let staged = match mode {
IW_MODE_INFRA => StagedMode::Station,
IW_MODE_MASTER => StagedMode::AccessPoint,
_ => return Err(AxError::InvalidInput),
};
with_pending(&ifname, |p| p.mode = Some(staged));
}
SIOCSIWESSID => {
let ssid = read_iw_point(arg, IW_ESSID_MAX_SIZE)?;
with_pending(&ifname, |p| p.ssid = Some(ssid));
}
SIOCSIWENCODEEXT => {
let key = read_iw_point(arg, MAX_PASSPHRASE)?;
let pass = String::from_utf8(key).map_err(|_| AxError::InvalidInput)?;
with_pending(&ifname, |p| p.passphrase = Some(pass));
}
SIOCSIWFREQ => {
let data = read_iwreq_data(arg)?;
let chan = u32::from_ne_bytes([data[0], data[1], data[2], data[3]]);
if chan == 0 || chan > 14 {
return Err(AxError::InvalidInput);
}
with_pending(&ifname, |p| p.channel = Some(chan as u8));
}
SIOCSIWCOMMIT => return commit(&ifname),
_ => return Err(AxError::Unsupported),
}
Ok(0)
}
fn commit(ifname: &str) -> AxResult<usize> {
let pending = take_pending(ifname).ok_or(AxError::InvalidInput)?;
let mode = pending.mode.ok_or(AxError::InvalidInput)?;
match mode {
StagedMode::Station => {
let ssid = pending.ssid.ok_or(AxError::InvalidInput)?;
let ssid = core::str::from_utf8(&ssid).map_err(|_| AxError::InvalidInput)?;
let password = pending.passphrase.unwrap_or_default();
ax_net::reconfigure_wifi(
ifname,
ax_net::WifiMode::Station {
ssid,
password: &password,
},
)?;
}
StagedMode::AccessPoint => {
let ssid = pending.ssid.ok_or(AxError::InvalidInput)?;
let channel = pending.channel.unwrap_or(AP_CHANNEL_DEFAULT);
ax_net::reconfigure_wifi(
ifname,
ax_net::WifiMode::AccessPoint {
ssid: &ssid,
channel,
ip: AP_SERVER_IP,
prefix_len: AP_PREFIX_LEN,
dhcp_client_ip: Some(AP_CLIENT_IP),
},
)?;
}
}
Ok(0)
}
#[allow(dead_code)]
fn _write_iwreq_data(arg: usize, data: &[u8]) -> AxResult<()> {
Ok(vm_write_slice((arg + IWREQ_DATA_OFFSET) as *mut u8, data)?)
}