xref: /aosp_15_r20/tools/netsim/rust/hostapd-rs/tests/integration_test.rs (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Integration tests for the `hostapd-rs` crate.
16 
17 use bytes::Bytes;
18 use hostapd_rs::hostapd::Hostapd;
19 use log::warn;
20 use netsim_packets::ieee80211::Ieee80211;
21 use pdl_runtime::Packet;
22 use std::{
23     env,
24     sync::mpsc,
25     thread,
26     time::{Duration, Instant},
27 };
28 
29 /// Initializes a `Hostapd` instance for testing.
30 ///
31 /// Returns a tuple containing the `Hostapd` instance and a receiver for
32 /// receiving data from `hostapd`.
init_test_hostapd() -> (Hostapd, mpsc::Receiver<Bytes>)33 fn init_test_hostapd() -> (Hostapd, mpsc::Receiver<Bytes>) {
34     let (tx, rx) = mpsc::channel();
35     let config_path = env::temp_dir().join("hostapd.conf");
36     (Hostapd::new(tx, true, config_path), rx)
37 }
38 
39 /// Waits for the `Hostapd` process to terminate.
terminate_hostapd(hostapd: &Hostapd)40 fn terminate_hostapd(hostapd: &Hostapd) {
41     hostapd.terminate();
42     let max_wait_time = Duration::from_secs(30);
43     let start_time = Instant::now();
44     while start_time.elapsed() < max_wait_time {
45         if !hostapd.is_running() {
46             break;
47         }
48         thread::sleep(Duration::from_millis(250));
49     }
50     warn!("Hostapd failed to terminate successfully within 30s");
51 }
52 
53 /// Hostapd integration test.
54 ///
55 /// A single test is used to avoid conflicts when multiple `hostapd` instances
56 /// run in parallel.
57 ///
58 /// TODO: Split up tests once feasible with `serial_test` crate or other methods.
59 #[test]
test_hostapd()60 fn test_hostapd() {
61     // Initialize a single Hostapd instance to share across tests to avoid >5s startup &
62     // shutdown overhead for every test
63     let (mut hostapd, receiver) = init_test_hostapd();
64     test_start(&mut hostapd);
65     test_receive_beacon_frame(&receiver);
66     test_get_and_set_ssid(&mut hostapd, &receiver);
67     test_terminate(&hostapd);
68 }
69 
70 /// Tests that `Hostapd` starts successfully.
test_start(hostapd: &mut Hostapd)71 fn test_start(hostapd: &mut Hostapd) {
72     hostapd.run();
73     assert!(hostapd.is_running());
74 }
75 
76 /// Tests that `Hostapd` terminates successfully.
test_terminate(hostapd: &Hostapd)77 fn test_terminate(hostapd: &Hostapd) {
78     terminate_hostapd(&hostapd);
79     assert!(!hostapd.is_running());
80 }
81 
82 /// Tests whether a beacon frame packet is received after `Hostapd` starts up.
test_receive_beacon_frame(receiver: &mpsc::Receiver<Bytes>)83 fn test_receive_beacon_frame(receiver: &mpsc::Receiver<Bytes>) {
84     let end_time = Instant::now() + Duration::from_secs(10);
85     loop {
86         // Try to receive a packet before end_time
87         match receiver.recv_timeout(end_time - Instant::now()) {
88             // Parse and verify received packet is beacon frame
89             Ok(packet) if Ieee80211::decode_full(&packet).unwrap().is_beacon() => break,
90             Ok(_) => continue, // Received a non beacon packet. Continue
91             _ => assert!(false, "Did not receive beacon frame in 10s"), // Error occurred
92         }
93     }
94 }
95 
96 /// Checks if the receiver receives a beacon frame with the specified SSID within 10 seconds.
verify_beacon_frame_ssid(receiver: &mpsc::Receiver<Bytes>, ssid: &str)97 fn verify_beacon_frame_ssid(receiver: &mpsc::Receiver<Bytes>, ssid: &str) {
98     let end_time = Instant::now() + Duration::from_secs(10);
99     loop {
100         // Try to receive a packet before end_time
101         match receiver.recv_timeout(end_time - Instant::now()) {
102             Ok(packet) => {
103                 if let Ok(beacon_ssid) =
104                     Ieee80211::decode_full(&packet).unwrap().get_ssid_from_beacon_frame()
105                 {
106                     if beacon_ssid == ssid {
107                         break; // Found expected beacon frame
108                     }
109                 }
110                 // Not expected beacon frame. Continue...
111             }
112             Err(mpsc::RecvTimeoutError::Timeout) => {
113                 assert!(false, "No Beacon frame received within 10s");
114             }
115             Err(mpsc::RecvTimeoutError::Disconnected) => {
116                 assert!(false, "Receiver disconnected while waiting for Beacon frame.");
117             }
118         }
119     }
120 }
121 
122 /// Tests various ways to configure `Hostapd` SSID and password.
test_get_and_set_ssid(hostapd: &mut Hostapd, receiver: &mpsc::Receiver<Bytes>)123 fn test_get_and_set_ssid(hostapd: &mut Hostapd, receiver: &mpsc::Receiver<Bytes>) {
124     // Check default ssid is set
125     let default_ssid = "AndroidWifi";
126     assert_eq!(hostapd.get_ssid(), default_ssid);
127 
128     let mut test_ssid = String::new();
129     let mut test_password = String::new();
130     // Verify set_ssid fails if SSID is empty
131     assert!(hostapd.set_ssid(&test_ssid, &test_password).is_err());
132 
133     // Verify set_ssid succeeds if SSID is not empty
134     test_ssid = "TestSsid".to_string();
135     assert!(hostapd.set_ssid(&test_ssid, &test_password).is_ok());
136     // Verify hostapd sends new beacon frame with updated SSID
137     verify_beacon_frame_ssid(receiver, &test_ssid);
138 
139     // Verify ssid was set successfully
140     assert_eq!(hostapd.get_ssid(), test_ssid);
141 
142     // Verify setting same ssid again succeeds
143     assert!(hostapd.set_ssid(&test_ssid, &test_password).is_ok());
144 
145     // Verify set_ssid fails if password is not empty
146     // TODO: Update once password support is implemented
147     test_password = "TestPassword".to_string();
148     assert!(hostapd.set_ssid(&test_ssid, &test_password).is_err());
149 }
150