1use crate::client::config_management::Credential;
6use crate::client::connection_selection::ConnectionSelectionRequester;
7use crate::client::roaming::lib::*;
8use crate::client::types;
9use crate::telemetry::{TelemetryEvent, TelemetrySender};
10use anyhow::{Error, format_err};
11use async_trait::async_trait;
12use fidl_fuchsia_wlan_common as fidl_common;
13use fidl_fuchsia_wlan_internal as fidl_internal;
14use futures::channel::mpsc;
15use futures::future::LocalBoxFuture;
16use futures::lock::Mutex;
17use futures::stream::{FuturesUnordered, StreamExt};
18use futures::{FutureExt, select};
19use log::{debug, error, info, warn};
20use std::any::Any;
21use std::sync::Arc;
22
23pub mod default_monitor;
24pub mod stationary_monitor;
25
26pub struct RoamDataSender {
28 sender: mpsc::Sender<RoamTriggerData>,
29}
30impl RoamDataSender {
31 pub fn new(trigger_data_sender: mpsc::Sender<RoamTriggerData>) -> Self {
32 Self { sender: trigger_data_sender }
33 }
34 pub fn send_signal_report_ind(
35 &mut self,
36 ind: fidl_internal::SignalReportIndication,
37 ) -> Result<(), anyhow::Error> {
38 Ok(self.sender.try_send(RoamTriggerData::SignalReportInd(ind))?)
39 }
40}
41#[async_trait(?Send)]
43pub trait RoamMonitorApi: Any {
44 async fn handle_roam_trigger_data(
48 &mut self,
49 data: RoamTriggerData,
50 ) -> Result<RoamTriggerDataOutcome, anyhow::Error>;
51 fn should_send_roam_request(&self, request: PolicyRoamRequest) -> Result<bool, anyhow::Error>;
54 fn notify_of_roam_attempt(&mut self);
57}
58
59pub async fn serve_roam_monitor(
62 mut roam_monitor: Box<dyn RoamMonitorApi>,
63 roaming_policy: RoamingPolicy,
64 mut trigger_data_receiver: mpsc::Receiver<RoamTriggerData>,
65 connection_selection_requester: ConnectionSelectionRequester,
66 mut roam_request_sender: mpsc::Sender<PolicyRoamRequest>,
67 telemetry_sender: TelemetrySender,
68 past_roams: Arc<Mutex<PastRoamList>>,
69) -> Result<(), anyhow::Error> {
70 let mut roam_search_result_futs: FuturesUnordered<
72 LocalBoxFuture<'static, Result<PolicyRoamRequest, Error>>,
73 > = FuturesUnordered::new();
74
75 loop {
76 select! {
77 trigger_data = trigger_data_receiver.next() => if let Some(data) = trigger_data {
79 match roam_monitor.handle_roam_trigger_data(data).await {
80 Ok(RoamTriggerDataOutcome::RoamSearch { scan_type, network_identifier, credential, current_security, reasons}) => {
81 telemetry_sender.send(TelemetryEvent::PolicyRoamScan { reasons: reasons.clone() });
82 info!("Performing scan to find proactive local roaming candidates.");
83 let roam_search_fut = get_roaming_connection_selection_future(
84 connection_selection_requester.clone(),
85 scan_type,
86 network_identifier,
87 credential,
88 current_security,
89 reasons
90 );
91 roam_search_result_futs.push(roam_search_fut.boxed());
92 },
93 Ok(RoamTriggerDataOutcome::Noop) => {},
94 Err(e) => error!("error handling roam trigger data: {}", e),
95 }
96 },
97 roam_search_result = roam_search_result_futs.select_next_some() => match roam_search_result {
100 Ok(request) => {
101 if roam_monitor.should_send_roam_request(request.clone()).unwrap_or_else(|e| {
102 error!("Error validating selected roam candidate: {}", e);
103 false
104 }) {
105 match roaming_policy {
106 RoamingPolicy::Enabled { mode: RoamingMode::CanRoam, ..} => {
107 info!("Requesting roam to candidate: {:?}", request.candidate.to_string_without_pii());
108 if roam_request_sender.try_send(request).is_err() {
109 warn!("Failed to send roam request, exiting monitor service loop.");
110 break
111 }
112 }
113 _ => {
114 debug!("Roaming policy is {:?}. Skipping roam request.", roaming_policy);
115 telemetry_sender.send(TelemetryEvent::WouldRoamConnect)
116 }
117 }
118 if let Some(mut past_roams) = past_roams.try_lock() {
120 past_roams.add(RoamEvent::new_roam_now());
121 } else {
122 error!("Unexpectedly failed to acquire lock on past roam list; will not record roam");
123 }
124 }
125 }
126 Err(e) => {
127 error!("Error occured during roam search: {:?}", e);
128 }
129 },
130 complete => {
131 debug!("Roam monitor channels dropped, exiting monitor service loop.");
132 break
133 }
134 }
135 }
136 Ok(())
137}
138
139async fn get_roaming_connection_selection_future(
142 mut connection_selection_requester: ConnectionSelectionRequester,
143 scan_type: fidl_common::ScanType,
144 network_identifier: types::NetworkIdentifier,
145 credential: Credential,
146 current_security: types::SecurityTypeDetailed,
147 reasons: Vec<RoamReason>,
148) -> Result<PolicyRoamRequest, Error> {
149 match connection_selection_requester
150 .do_roam_selection(scan_type, network_identifier, credential, current_security)
151 .await?
152 {
153 Some(candidate) => Ok(PolicyRoamRequest { candidate, reasons }),
154 None => Err(format_err!("No roam candidates found.")),
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use super::*;
161 use crate::client::connection_selection::ConnectionSelectionRequest;
162 use crate::client::roaming::lib::RoamingProfile;
163 use crate::telemetry::TelemetryEvent;
164 use crate::util::testing::fakes::FakeRoamMonitor;
165 use crate::util::testing::{
166 generate_random_network_identifier, generate_random_password,
167 generate_random_scanned_candidate,
168 };
169 use assert_matches::assert_matches;
170 use fidl_fuchsia_wlan_common as fidl_common;
171 use fidl_fuchsia_wlan_internal as fidl_internal;
172 use fuchsia_async::{self as fasync, TestExecutor};
173 use futures::task::Poll;
174 use futures::{Future, pin_mut};
175 use std::pin::Pin;
176 use test_case::test_case;
177
178 struct TestValues {
179 trigger_data_sender: mpsc::Sender<RoamTriggerData>,
180 trigger_data_receiver: mpsc::Receiver<RoamTriggerData>,
181 roam_request_sender: mpsc::Sender<PolicyRoamRequest>,
182 roam_request_receiver: mpsc::Receiver<PolicyRoamRequest>,
183 connection_selection_requester: ConnectionSelectionRequester,
184 connection_selection_request_receiver: mpsc::Receiver<ConnectionSelectionRequest>,
185 telemetry_sender: TelemetrySender,
186 telemetry_receiver: mpsc::Receiver<TelemetryEvent>,
187 past_roams: Arc<Mutex<PastRoamList>>,
188 }
189
190 fn setup_test() -> TestValues {
191 let (trigger_data_sender, trigger_data_receiver) = mpsc::channel(100);
192 let (roam_sender, roam_receiver) = mpsc::channel(100);
193 let (connection_selection_request_sender, connection_selection_request_receiver) =
194 mpsc::channel(5);
195 let connection_selection_requester =
196 ConnectionSelectionRequester::new(connection_selection_request_sender);
197 let (telemetry_sender, telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
198 let telemetry_sender = TelemetrySender::new(telemetry_sender);
199 let past_roams = Arc::new(Mutex::new(PastRoamList::new()));
200 TestValues {
201 trigger_data_sender,
202 trigger_data_receiver,
203 roam_request_sender: roam_sender,
204 roam_request_receiver: roam_receiver,
205 connection_selection_requester,
206 connection_selection_request_receiver,
207 telemetry_sender,
208 telemetry_receiver,
209 past_roams,
210 }
211 }
212
213 #[fuchsia::test]
214 fn test_roam_data_sender_send_signal_report_ind() {
215 let _exec = TestExecutor::new();
216 let (sender, mut receiver) = mpsc::channel(100);
217 let mut roam_data_sender = RoamDataSender::new(sender);
218 let ind =
219 fidl_internal::SignalReportIndication { rssi_dbm: -60, snr_db: 30, tx_rate_500kbps: 0 };
220
221 roam_data_sender.send_signal_report_ind(ind).expect("error sending signal report");
222
223 assert_matches!(receiver.try_recv(), Ok(RoamTriggerData::SignalReportInd(data)) => {
225 assert_eq!(ind, data);
226 });
227 }
228
229 #[test_case(RoamTriggerDataOutcome::Noop; "should not queue roam search")]
230 #[test_case(RoamTriggerDataOutcome::RoamSearch { scan_type: fidl_common::ScanType::Passive, network_identifier: generate_random_network_identifier(), credential: generate_random_password(), current_security: types::SecurityTypeDetailed::Open, reasons: vec![]}; "should queue roam search")]
231 #[fuchsia::test(add_test_attr = false)]
232 fn test_serve_loop_handles_trigger_data(response_to_should_roam_scan: RoamTriggerDataOutcome) {
233 let mut exec = TestExecutor::new();
234 let mut test_values = setup_test();
235
236 let mut roam_monitor = FakeRoamMonitor::new();
239 roam_monitor.response_to_should_roam_scan = response_to_should_roam_scan.clone();
240
241 let serve_fut = serve_roam_monitor(
243 Box::new(roam_monitor),
244 RoamingPolicy::Enabled {
245 profile: RoamingProfile::Stationary,
246 mode: RoamingMode::CanRoam,
247 },
248 test_values.trigger_data_receiver,
249 test_values.connection_selection_requester,
250 test_values.roam_request_sender,
251 test_values.telemetry_sender,
252 test_values.past_roams,
253 );
254 pin_mut!(serve_fut);
255
256 assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
258
259 test_values
262 .trigger_data_sender
263 .try_send(RoamTriggerData::SignalReportInd(fidl_internal::SignalReportIndication {
264 rssi_dbm: -40,
265 snr_db: 40,
266 tx_rate_500kbps: 0,
267 }))
268 .expect("failed to send");
269
270 assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
272
273 match response_to_should_roam_scan {
274 RoamTriggerDataOutcome::RoamSearch { .. } => {
275 assert_matches!(
277 test_values.telemetry_receiver.try_recv(),
278 Ok(TelemetryEvent::PolicyRoamScan { .. })
279 );
280 assert_matches!(
282 test_values.connection_selection_request_receiver.try_recv(),
283 Ok(_)
284 );
285 }
286 RoamTriggerDataOutcome::Noop => {
287 assert_matches!(
289 test_values.connection_selection_request_receiver.try_recv(),
290 Err(_)
291 );
292 }
293 }
294 }
295
296 #[test_case(false, RoamingMode::CanRoam; "should not send roam request can roam")]
297 #[test_case(false, RoamingMode::MetricsOnly; "should not send roam request metrics only")]
298 #[test_case(true, RoamingMode::CanRoam; "should send roam request can roam")]
299 #[test_case(true, RoamingMode::MetricsOnly; "should send roam request metrics only")]
300 #[fuchsia::test(add_test_attr = false)]
301 fn test_serve_loop_handles_roam_search_results(
302 response_to_should_send_roam_request: bool,
303 roaming_mode: RoamingMode,
304 ) {
305 let mut exec = TestExecutor::new();
306 let mut test_values = setup_test();
307
308 let mut roam_monitor = FakeRoamMonitor::new();
311 roam_monitor.response_to_should_roam_scan = RoamTriggerDataOutcome::RoamSearch {
312 scan_type: fidl_common::ScanType::Passive,
313 network_identifier: generate_random_network_identifier(),
314 credential: generate_random_password(),
315 current_security: types::SecurityTypeDetailed::Open,
316 reasons: vec![],
317 };
318 roam_monitor.response_to_should_send_roam_request = response_to_should_send_roam_request;
319
320 let serve_fut = serve_roam_monitor(
322 Box::new(roam_monitor),
323 RoamingPolicy::Enabled { profile: RoamingProfile::Stationary, mode: roaming_mode },
324 test_values.trigger_data_receiver,
325 test_values.connection_selection_requester,
326 test_values.roam_request_sender,
327 test_values.telemetry_sender,
328 test_values.past_roams,
329 );
330 pin_mut!(serve_fut);
331
332 assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
334
335 test_values
338 .trigger_data_sender
339 .try_send(RoamTriggerData::SignalReportInd(fidl_internal::SignalReportIndication {
340 rssi_dbm: -40,
341 snr_db: 40,
342 tx_rate_500kbps: 0,
343 }))
344 .expect("failed to send");
345
346 assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
348
349 let candidate = generate_random_scanned_candidate();
351 assert_matches!(test_values.connection_selection_request_receiver.try_recv(), Ok(ConnectionSelectionRequest::RoamSelection { responder, .. }) => {
352 responder.send(Some(candidate.clone())).expect("failed to send");
354 });
355
356 assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
358
359 assert_matches!(
361 test_values.telemetry_receiver.try_recv(),
362 Ok(TelemetryEvent::PolicyRoamScan { .. })
363 );
364
365 if response_to_should_send_roam_request && roaming_mode == RoamingMode::CanRoam {
366 assert_matches!(test_values.roam_request_receiver.try_recv(), Ok(selection) => {
369 assert_eq!(selection.candidate, candidate);
370 });
371 } else {
372 assert_matches!(test_values.roam_request_receiver.try_recv(), Err(_));
375 }
376 }
377
378 #[fuchsia::test]
379 fn test_roam_attempts_are_recorded_in_past_roams() {
380 let mut exec = TestExecutor::new();
381 let mut test_values = setup_test();
382
383 let mut roam_monitor = FakeRoamMonitor::new();
386 roam_monitor.response_to_should_roam_scan = RoamTriggerDataOutcome::RoamSearch {
387 scan_type: fidl_common::ScanType::Passive,
388 network_identifier: generate_random_network_identifier(),
389 credential: generate_random_password(),
390 current_security: types::SecurityTypeDetailed::Open,
391 reasons: vec![],
392 };
393 roam_monitor.response_to_should_send_roam_request = true;
394
395 let serve_fut = serve_roam_monitor(
397 Box::new(roam_monitor),
398 RoamingPolicy::Enabled {
399 profile: RoamingProfile::Stationary,
400 mode: RoamingMode::CanRoam,
401 },
402 test_values.trigger_data_receiver,
403 test_values.connection_selection_requester,
404 test_values.roam_request_sender,
405 test_values.telemetry_sender,
406 test_values.past_roams.clone(),
407 );
408 pin_mut!(serve_fut);
409
410 trigger_scan_and_roam(
411 &mut exec,
412 &mut serve_fut,
413 &mut test_values.trigger_data_sender,
414 &mut test_values.connection_selection_request_receiver,
415 &mut test_values.roam_request_receiver,
416 );
417
418 let past_roams = test_values
420 .past_roams
421 .clone()
422 .try_lock()
423 .unwrap()
424 .get_recent(fasync::MonotonicInstant::INFINITE_PAST);
425 assert_eq!(past_roams.len(), 1);
426
427 trigger_scan_and_roam(
428 &mut exec,
429 &mut serve_fut,
430 &mut test_values.trigger_data_sender,
431 &mut test_values.connection_selection_request_receiver,
432 &mut test_values.roam_request_receiver,
433 );
434
435 let past_roams = test_values
437 .past_roams
438 .clone()
439 .try_lock()
440 .unwrap()
441 .get_recent(fasync::MonotonicInstant::INFINITE_PAST);
442 assert_eq!(past_roams.len(), 2);
443 }
444
445 fn trigger_scan_and_roam(
449 exec: &mut TestExecutor,
450 serve_fut: &mut Pin<&mut impl Future<Output = std::result::Result<(), anyhow::Error>>>,
451 trigger_data_sender: &mut mpsc::Sender<RoamTriggerData>,
452 connection_selection_request_receiver: &mut mpsc::Receiver<ConnectionSelectionRequest>,
453 roam_request_receiver: &mut mpsc::Receiver<PolicyRoamRequest>,
454 ) {
455 assert_matches!(exec.run_until_stalled(serve_fut), Poll::Pending);
457
458 trigger_data_sender
461 .try_send(RoamTriggerData::SignalReportInd(fidl_internal::SignalReportIndication {
462 rssi_dbm: -40,
463 snr_db: 40,
464 tx_rate_500kbps: 0,
465 }))
466 .expect("failed to send");
467
468 assert_matches!(exec.run_until_stalled(serve_fut), Poll::Pending);
470
471 let candidate = generate_random_scanned_candidate();
473 assert_matches!(connection_selection_request_receiver.try_recv(), Ok(ConnectionSelectionRequest::RoamSelection { responder, .. }) => {
474 responder.send(Some(candidate.clone())).expect("failed to send");
476 });
477
478 assert_matches!(exec.run_until_stalled(serve_fut), Poll::Pending);
479 assert_matches!(roam_request_receiver.try_recv(), Ok(selection) => {
480 assert_eq!(selection.candidate, candidate);
481 });
482 }
483}