wlancfg_lib/client/roaming/local_roam_manager/
mod.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::client::config_management::Credential;
6use crate::client::connection_selection::ConnectionSelectionRequester;
7use crate::client::roaming::lib::*;
8use crate::client::roaming::roam_monitor;
9use crate::client::types;
10use crate::config_management::SavedNetworksManagerApi;
11use crate::telemetry::TelemetrySender;
12use anyhow::Error;
13use futures::channel::mpsc;
14use futures::future::LocalBoxFuture;
15use futures::lock::Mutex;
16use futures::stream::FuturesUnordered;
17use futures::{select, StreamExt};
18use log::{debug, error};
19use std::convert::Infallible;
20use std::sync::Arc;
21
22// Create a roam monitor implementation based on the roaming profile.
23fn create_roam_monitor(
24    roaming_policy: RoamingPolicy,
25    ap_state: types::ApState,
26    network_identifier: types::NetworkIdentifier,
27    credential: Credential,
28    telemetry_sender: TelemetrySender,
29    saved_networks: Arc<dyn SavedNetworksManagerApi>,
30    past_roams: Arc<Mutex<PastRoamList>>,
31) -> Box<dyn roam_monitor::RoamMonitorApi> {
32    match roaming_policy {
33        RoamingPolicy::Enabled { profile: RoamingProfile::Stationary, .. } => {
34            Box::new(roam_monitor::stationary_monitor::StationaryMonitor::new(
35                ap_state,
36                network_identifier,
37                credential,
38                telemetry_sender,
39                saved_networks,
40                past_roams,
41            ))
42        }
43        RoamingPolicy::Disabled => {
44            Box::new(roam_monitor::default_monitor::DefaultRoamMonitor::new())
45        }
46    }
47}
48
49// Requests that can be made to roam manager service loop.
50#[cfg_attr(test, derive(Debug))]
51pub enum RoamServiceRequest {
52    InitializeRoamMonitor {
53        ap_state: types::ApState,
54        network_identifier: types::NetworkIdentifier,
55        credential: Credential,
56        roam_request_sender: mpsc::Sender<PolicyRoamRequest>,
57        roam_trigger_data_receiver: mpsc::Receiver<RoamTriggerData>,
58    },
59}
60
61// Allows callers to make roam service requests.
62#[derive(Clone)]
63pub struct RoamManager {
64    sender: mpsc::Sender<RoamServiceRequest>,
65}
66
67impl RoamManager {
68    pub fn new(sender: mpsc::Sender<RoamServiceRequest>) -> Self {
69        Self { sender }
70    }
71    // Create a roam monitor and return handles for passing trigger data and roam requests .
72    pub fn initialize_roam_monitor(
73        &mut self,
74        ap_state: types::ApState,
75        network_identifier: types::NetworkIdentifier,
76        credential: Credential,
77        roam_request_sender: mpsc::Sender<PolicyRoamRequest>,
78    ) -> roam_monitor::RoamDataSender {
79        let (roam_trigger_data_sender, roam_trigger_data_receiver) =
80            mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
81        let _ = self
82            .sender
83            .try_send(RoamServiceRequest::InitializeRoamMonitor {
84                ap_state,
85                network_identifier,
86                credential,
87                roam_request_sender,
88                roam_trigger_data_receiver,
89            })
90            .inspect_err(|e| {
91                error!("Failed to request roam monitoring: {}. Proceeding without roaming.", e)
92            });
93        roam_monitor::RoamDataSender::new(roam_trigger_data_sender)
94    }
95}
96
97/// Service loop for handling roam manager requests.
98pub async fn serve_local_roam_manager_requests(
99    roaming_policy: RoamingPolicy,
100    mut roam_service_request_receiver: mpsc::Receiver<RoamServiceRequest>,
101    connection_selection_requester: ConnectionSelectionRequester,
102    telemetry_sender: TelemetrySender,
103    saved_networks: Arc<dyn SavedNetworksManagerApi>,
104) -> Result<Infallible, Error> {
105    // Queue of created monitor futures.
106    let mut monitor_futs: FuturesUnordered<LocalBoxFuture<'static, Result<(), anyhow::Error>>> =
107        FuturesUnordered::new();
108    let past_roams = Arc::new(Mutex::new(PastRoamList::new(NUM_PLATFORM_MAX_ROAMS_PER_DAY)));
109
110    loop {
111        select! {
112            // Handle requests sent to roam manager service loop.
113            req = roam_service_request_receiver.select_next_some() => {
114                match req {
115                    // Create and start a roam monitor future, passing in handles from caller. This
116                    // ensures that new data is initialized for every new caller (e.g. connected_state).
117                    RoamServiceRequest::InitializeRoamMonitor { ap_state, network_identifier, credential, roam_request_sender, roam_trigger_data_receiver }=> {
118                        let monitor = create_roam_monitor(roaming_policy, ap_state, network_identifier, credential, telemetry_sender.clone(), saved_networks.clone(), past_roams.clone());
119                        let monitor_fut = roam_monitor::serve_roam_monitor(monitor, roaming_policy, roam_trigger_data_receiver, connection_selection_requester.clone(), roam_request_sender.clone(), telemetry_sender.clone(), past_roams.clone());
120                        monitor_futs.push(Box::pin(monitor_fut));
121                    }
122                }
123            }
124            // Serves roam monitors. Futures return when when caller drops handle.
125            _terminated_monitor = monitor_futs.select_next_some() => {
126                debug!("Roam monitor future closed.");
127            }
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::telemetry::TelemetryEvent;
136    use crate::util::testing::{
137        generate_random_ap_state, generate_random_network_identifier, generate_random_password,
138        generate_random_roaming_connection_data, FakeSavedNetworksManager,
139    };
140    use assert_matches::assert_matches;
141    use fidl_fuchsia_wlan_internal::SignalReportIndication;
142    use fuchsia_async::TestExecutor;
143    use futures::task::Poll;
144    use std::pin::pin;
145    use test_case::test_case;
146
147    struct RoamManagerServiceTestValues {
148        telemetry_sender: TelemetrySender,
149        connection_selection_requester: ConnectionSelectionRequester,
150        roam_manager: RoamManager,
151        roam_service_request_receiver: mpsc::Receiver<RoamServiceRequest>,
152        saved_networks: Arc<FakeSavedNetworksManager>,
153    }
154
155    fn setup_test() -> RoamManagerServiceTestValues {
156        let (telemetry_sender, _) = mpsc::channel::<TelemetryEvent>(100);
157        let telemetry_sender = TelemetrySender::new(telemetry_sender);
158        let (connection_selection_request_sender, _) = mpsc::channel(5);
159        let connection_selection_requester =
160            ConnectionSelectionRequester::new(connection_selection_request_sender);
161        let (roam_service_request_sender, roam_service_request_receiver) = mpsc::channel(100);
162        let roam_manager = RoamManager::new(roam_service_request_sender);
163        let saved_networks = Arc::new(FakeSavedNetworksManager::new());
164        RoamManagerServiceTestValues {
165            telemetry_sender,
166            connection_selection_requester,
167            roam_manager,
168            roam_service_request_receiver,
169            saved_networks,
170        }
171    }
172
173    #[test_case(RoamingPolicy::Disabled; "disabled")]
174    #[test_case(RoamingPolicy::Enabled { profile: RoamingProfile::Stationary, mode: RoamingMode::CanRoam }; "enabled_stationary_can_roam")]
175    #[fuchsia::test(add_test_attr = false)]
176    fn test_create_roam_monitor(roaming_policy: RoamingPolicy) {
177        let _exec = TestExecutor::new();
178        let (telemetry_sender, _) = mpsc::channel::<TelemetryEvent>(100);
179        let telemetry_sender = TelemetrySender::new(telemetry_sender);
180        let saved_networks = Arc::new(FakeSavedNetworksManager::new());
181        let past_roams = Arc::new(Mutex::new(PastRoamList::new(NUM_PLATFORM_MAX_ROAMS_PER_DAY)));
182        let monitor = create_roam_monitor(
183            roaming_policy,
184            generate_random_ap_state(),
185            generate_random_network_identifier(),
186            generate_random_password(),
187            telemetry_sender.clone(),
188            saved_networks.clone(),
189            past_roams.clone(),
190        );
191        match roaming_policy {
192            RoamingPolicy::Disabled => {
193                let default_monitor: Box<dyn roam_monitor::RoamMonitorApi> =
194                    Box::new(roam_monitor::default_monitor::DefaultRoamMonitor::new());
195                assert_eq!(default_monitor.type_id(), monitor.type_id());
196            }
197            RoamingPolicy::Enabled { profile: RoamingProfile::Stationary, .. } => {
198                let stationary_monitor: Box<dyn roam_monitor::RoamMonitorApi> =
199                    Box::new(roam_monitor::stationary_monitor::StationaryMonitor::new(
200                        generate_random_ap_state(),
201                        generate_random_network_identifier(),
202                        generate_random_password(),
203                        telemetry_sender.clone(),
204                        saved_networks,
205                        past_roams.clone(),
206                    ));
207                assert_eq!(stationary_monitor.type_id(), monitor.type_id());
208            }
209        }
210    }
211
212    #[test_case(RoamingPolicy::Disabled; "disabled")]
213    #[test_case(RoamingPolicy::Enabled { profile: RoamingProfile::Stationary, mode: RoamingMode::CanRoam }; "enabled_stationary_can_roam")]
214    #[fuchsia::test(add_test_attr = false)]
215    fn test_roam_manager_handles_get_roam_monitor_request(roaming_policy: RoamingPolicy) {
216        let mut exec = TestExecutor::new();
217        let mut test_values = setup_test();
218        let mut serve_fut = pin!(serve_local_roam_manager_requests(
219            roaming_policy,
220            test_values.roam_service_request_receiver,
221            test_values.connection_selection_requester.clone(),
222            test_values.telemetry_sender.clone(),
223            test_values.saved_networks,
224        ));
225        assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
226
227        // Send a request to initialize a roam monitor
228        let connection_data = generate_random_roaming_connection_data();
229        let (sender, _) = mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
230        let mut roam_monitor_sender = test_values.roam_manager.initialize_roam_monitor(
231            connection_data.ap_state.clone(),
232            generate_random_network_identifier(),
233            generate_random_password(),
234            sender,
235        );
236
237        assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
238
239        // Send some data via one of the roam monitor. Ensure that it doesn't error out, which means
240        // a created roam monitor is holding the receiver end.
241        roam_monitor_sender
242            .send_signal_report_ind(SignalReportIndication { rssi_dbm: -60, snr_db: 30 })
243            .expect("error sending data via roam monitor sender");
244    }
245
246    #[test_case(RoamingPolicy::Disabled; "disabled")]
247    #[test_case(RoamingPolicy::Enabled { profile: RoamingProfile::Stationary, mode: RoamingMode::CanRoam }; "enabled_stationary_can_roam")]
248    #[fuchsia::test]
249    fn test_roam_manager_handles_terminated_roam_monitors(roaming_policy: RoamingPolicy) {
250        let mut exec = TestExecutor::new();
251        let mut test_values = setup_test();
252        let mut serve_fut = pin!(serve_local_roam_manager_requests(
253            roaming_policy,
254            test_values.roam_service_request_receiver,
255            test_values.connection_selection_requester.clone(),
256            test_values.telemetry_sender.clone(),
257            test_values.saved_networks,
258        ));
259        assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
260
261        // Send a request to initialize a roam monitor
262        let connection_data = generate_random_roaming_connection_data();
263        let (sender, _) = mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
264        let mut roam_monitor_sender = test_values.roam_manager.initialize_roam_monitor(
265            connection_data.ap_state.clone(),
266            generate_random_network_identifier(),
267            generate_random_password(),
268            sender,
269        );
270
271        assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
272
273        // Send some trigger data over a channel. Ensure that it doesn't error out, which means
274        // a created roam monitor is holding the receiver end.
275        roam_monitor_sender
276            .send_signal_report_ind(SignalReportIndication { rssi_dbm: -60, snr_db: 30 })
277            .expect("error sending data via roam monitor sender");
278
279        assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
280
281        // Drop the trigger data sender. This should cause the monitor to terminate?
282        std::mem::drop(roam_monitor_sender);
283
284        assert_matches!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
285    }
286}