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