Merge "Implement initial hostapd set_ssid()" into main
diff --git a/rust/hostapd-rs/src/hostapd.rs b/rust/hostapd-rs/src/hostapd.rs
index 72a5e06..6a96d77 100644
--- a/rust/hostapd-rs/src/hostapd.rs
+++ b/rust/hostapd-rs/src/hostapd.rs
@@ -19,8 +19,9 @@
///
/// hostapd.conf file is generated under discovery directory.
///
+use anyhow::bail;
use bytes::Bytes;
-use log::warn;
+use log::{info, warn};
use std::collections::HashMap;
use std::ffi::{c_char, c_int, CStr, CString};
use std::fs::File;
@@ -42,7 +43,8 @@
use tokio::sync::Mutex;
use crate::hostapd_sys::{
- run_hostapd_main, set_virtio_ctrl_sock, set_virtio_sock, VIRTIO_WIFI_CTRL_CMD_TERMINATE,
+ run_hostapd_main, set_virtio_ctrl_sock, set_virtio_sock, VIRTIO_WIFI_CTRL_CMD_RELOAD_CONFIG,
+ VIRTIO_WIFI_CTRL_CMD_TERMINATE,
};
/// Alias for RawFd on Unix or RawSocket on Windows (converted to i32)
@@ -65,7 +67,7 @@
impl Hostapd {
pub fn new(tx_bytes: mpsc::Sender<Bytes>, verbose: bool, config_path: PathBuf) -> Self {
// Default Hostapd conf entries
- let config_data = vec![
+ let config_data = [
("ssid", "AndroidWifi"),
("interface", "wlan1"),
("driver", "virtio_wifi"),
@@ -86,7 +88,7 @@
("eapol_key_index_workaround", "0"),
];
let mut config: HashMap<String, String> = HashMap::new();
- config.extend(config_data.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
+ config.extend(config_data.iter().map(|(k, v)| (k.to_string(), v.to_string())));
Hostapd {
handle: RwLock::new(None),
@@ -143,12 +145,53 @@
true
}
- pub fn set_ssid(&mut self, _ssid: String, _password: String) -> bool {
- todo!();
+ /// Reconfigure Hostapd with specified SSID (and password) config
+ ///
+ /// TODO:
+ /// * implement password & encryption support
+ /// * update as async fn.
+ pub fn set_ssid(&mut self, ssid: String, password: String) -> anyhow::Result<()> {
+ if ssid.is_empty() {
+ bail!("set_ssid must have a non-empty SSID");
+ }
+
+ if !password.is_empty() {
+ bail!("set_ssid with password is not yet supported.");
+ }
+
+ if ssid == self.get_ssid() && password == self.get_config_val("password") {
+ info!("SSID and password matches current configuration.");
+ return Ok(());
+ }
+
+ // Update the config
+ self.config.insert("ssid".to_string(), ssid);
+ if !password.is_empty() {
+ let password_config = [
+ ("wpa", "2"),
+ ("wpa_key_mgmt", "WPA-PSK"),
+ ("rsn_pairwise", "CCMP"),
+ ("wpa_passphrase", &password),
+ ];
+ self.config.extend(password_config.iter().map(|(k, v)| (k.to_string(), v.to_string())));
+ }
+
+ // Update the config file.
+ self.gen_config_file()?;
+
+ // Send command for Hostapd to reload config file
+ if let Err(e) = get_runtime().block_on(Self::async_write(
+ self.ctrl_writer.as_ref().unwrap(),
+ c_string_to_bytes(VIRTIO_WIFI_CTRL_CMD_RELOAD_CONFIG),
+ )) {
+ bail!("Failed to send VIRTIO_WIFI_CTRL_CMD_RELOAD_CONFIG to hostapd to reload config: {:?}", e);
+ }
+
+ Ok(())
}
- pub fn get_ssid(&self) -> Option<String> {
- self.config.get("ssid").cloned()
+ pub fn get_ssid(&self) -> String {
+ self.get_config_val("ssid")
}
/// Input data packet bytes from netsim to hostapd
@@ -194,6 +237,13 @@
Ok(writer.flush()?) // Ensure all data is written to the file
}
+ /// Get the value of the given key in the config
+ ///
+ /// Returns empty String if key is not found
+ fn get_config_val(&self, key: &str) -> String {
+ self.config.get(key).cloned().unwrap_or_default()
+ }
+
/// Creates a pipe of two connected TcpStream objects
///
/// Extracts the first stream's raw descriptor and splits the second stream as OwnedReadHalf and OwnedWriteHalf
@@ -285,7 +335,7 @@
}
};
- if let Err(e) = tx_bytes.send(Bytes::from(buf[..size].to_vec())) {
+ if let Err(e) = tx_bytes.send(Bytes::copy_from_slice(&buf[..size])) {
warn!("Failed to send hostapd packet response: {:?}", e);
break;
};
diff --git a/rust/hostapd-rs/tests/integration_test.rs b/rust/hostapd-rs/tests/integration_test.rs
index fdf36d5..85eeb9a 100644
--- a/rust/hostapd-rs/tests/integration_test.rs
+++ b/rust/hostapd-rs/tests/integration_test.rs
@@ -21,16 +21,32 @@
time::{Duration, Instant},
};
-/// Test whether hostapd starts and terminates successfully
-#[test]
-fn test_hostapd_run_and_terminate() {
+/// Helper function to initialize Hostapd for test
+fn init_test_hostapd() -> Hostapd {
let (tx, _rx): (mpsc::Sender<Bytes>, mpsc::Receiver<Bytes>) = mpsc::channel();
let config_path = env::temp_dir().join("hostapd.conf");
- let mut hostapd = Hostapd::new(tx, true, config_path);
+ Hostapd::new(tx, true, config_path)
+}
+
+/// Hostpad integration test
+///
+/// A single test is used here to avoid conflicts when multiple hostapd runs in parallel
+/// TODO: Split up tests once serial_test crate is available
+#[test]
+fn test_hostapd() {
+ test_start_and_terminate();
+ test_set_ssid();
+}
+
+/// Test Hostapd starts and terminates successfully
+fn test_start_and_terminate() {
+ // Test hostapd starts successfully
+ let mut hostapd = init_test_hostapd();
hostapd.run();
assert!(hostapd.is_running());
+
+ // Test hostapd terminates successfully
hostapd.terminate();
- // Check that hostapd terminates within 30s
let max_wait_time = Duration::from_secs(30);
let start_time = Instant::now();
while start_time.elapsed() < max_wait_time {
@@ -41,3 +57,34 @@
}
assert!(!hostapd.is_running());
}
+
+/// Test various ways to configure Hostapd SSID and password
+fn test_set_ssid() {
+ let mut hostapd = init_test_hostapd();
+ hostapd.run();
+ assert!(hostapd.is_running());
+
+ // Check default ssid is set
+ assert_eq!(hostapd.get_ssid(), "AndroidWifi");
+ let mut test_ssid = String::new();
+ let mut test_password = String::new();
+
+ // Verify set_ssid fails if SSID is empty
+ assert!(hostapd.set_ssid(test_ssid.clone(), test_password.clone()).is_err());
+
+ // Verify set_ssid succeeds if SSID is not empty
+ test_ssid = "TestSsid".to_string();
+ assert!(hostapd.set_ssid(test_ssid.clone(), test_password.clone()).is_ok());
+ // TODO: Enhance test to verify hostapd response packet SSID
+
+ // Verify ssid was set successfully
+ assert_eq!(hostapd.get_ssid(), test_ssid.clone());
+
+ // Verify setting same ssid again succeeds
+ assert!(hostapd.set_ssid(test_ssid.clone(), test_password.clone()).is_ok());
+
+ // Verify set_ssid fails if password is not empty
+ // TODO: Update once password support is implemented
+ test_password = "TestPassword".to_string();
+ assert!(hostapd.set_ssid(test_ssid.clone(), test_password.clone()).is_err());
+}