Skip to main content

recovery_util/
wlan.rs

1// Copyright 2022 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 anyhow::{bail, format_err, Context as _, Error};
6use async_trait::async_trait;
7use fidl::endpoints::{create_proxy, create_request_stream};
8use fidl_fuchsia_wlan_policy::{self as wlan_policy, NetworkConfig, SecurityType};
9use fuchsia_async::{MonotonicInstant, TimeoutExt as _};
10use fuchsia_component::client::connect_to_protocol;
11use futures::TryStreamExt as _;
12use zx::MonotonicDuration;
13
14const CONNECT_TIMEOUT: MonotonicDuration = MonotonicDuration::from_seconds(60);
15
16type GetClientController = dyn Fn() -> Result<
17    (wlan_policy::ClientControllerProxy, wlan_policy::ClientStateUpdatesRequestStream),
18    Error,
19>;
20
21pub fn create_network_info(
22    ssid: &str,
23    pass: Option<&str>,
24    security_type: Option<wlan_policy::SecurityType>,
25) -> NetworkConfig {
26    let credential = pass.map_or(wlan_policy::Credential::None(wlan_policy::Empty), |pass| {
27        wlan_policy::Credential::Password(pass.as_bytes().to_vec())
28    });
29    let network_id = wlan_policy::NetworkIdentifier {
30        ssid: ssid.as_bytes().to_vec(),
31        type_: security_type.unwrap_or(wlan_policy::SecurityType::None),
32    };
33    wlan_policy::NetworkConfig {
34        id: Some(network_id),
35        credential: Some(credential),
36        ..Default::default()
37    }
38}
39
40fn get_client_controller(
41) -> Result<(wlan_policy::ClientControllerProxy, wlan_policy::ClientStateUpdatesRequestStream), Error>
42{
43    let policy_provider = connect_to_protocol::<wlan_policy::ClientProviderMarker>()?;
44    let (client_controller, server_end) = create_proxy::<wlan_policy::ClientControllerMarker>();
45    let (update_client_end, update_stream) =
46        create_request_stream::<wlan_policy::ClientStateUpdatesMarker>();
47    policy_provider
48        .get_controller(server_end, update_client_end)
49        .context("PolicyProvider.get_controller")?;
50
51    Ok((client_controller, update_stream))
52}
53
54#[async_trait(? Send)]
55pub trait WifiConnect {
56    async fn scan_for_networks(&self) -> Result<Vec<NetworkInfo>, Error>;
57    async fn connect(&self, network: NetworkConfig) -> Result<(), Error>;
58}
59
60pub struct WifiConnectImpl {
61    get_client_controller: Box<GetClientController>,
62}
63
64impl WifiConnectImpl {
65    pub fn new() -> Self {
66        Self { get_client_controller: Box::new(get_client_controller) }
67    }
68
69    async fn wait_for_connection(
70        &self,
71        mut client_state_updates_request: wlan_policy::ClientStateUpdatesRequestStream,
72    ) -> Result<(), Error> {
73        while let Some(update_request) = client_state_updates_request.try_next().await? {
74            let update = update_request.into_on_client_state_update();
75            let (update, responder) = match update {
76                Some((update, responder)) => (update, responder),
77                None => return Err(format_err!("Client provider produced invalid update.")),
78            };
79            let _ = responder.send();
80            if let Some(networks) = update.networks {
81                if networks
82                    .iter()
83                    .any(|ns| ns.state == Some(wlan_policy::ConnectionState::Connected))
84                {
85                    // Connected to a WiFi network
86                    break;
87                }
88            }
89        }
90
91        Ok(())
92    }
93}
94
95#[derive(Clone, Debug, PartialEq)]
96pub struct NetworkInfo {
97    pub ssid: String,
98    pub rssi: i8,
99    pub security_type: SecurityType,
100}
101
102#[async_trait(? Send)]
103impl WifiConnect for WifiConnectImpl {
104    async fn scan_for_networks(&self) -> Result<Vec<NetworkInfo>, Error> {
105        let mut networks: Vec<NetworkInfo> = Vec::new();
106        let (client_controller, _) = (self.get_client_controller)()?;
107        let scan_results = handle_scan(client_controller).await?;
108        for network in scan_results {
109            if let Some(id) = network.id {
110                let ssid = String::from_utf8(id.ssid).unwrap();
111                if network.entries.is_some() && !ssid.is_empty() {
112                    let mut best_rssi = -128;
113                    for entry in network.entries.unwrap() {
114                        let rssi = match entry.rssi {
115                            Some(rssi) => rssi,
116                            None => 0,
117                        };
118                        if rssi > best_rssi {
119                            best_rssi = rssi;
120                        }
121                    }
122                    networks.push(NetworkInfo { ssid, rssi: best_rssi, security_type: id.type_ });
123                }
124            }
125        }
126
127        networks.sort_by(|a, b| b.rssi.cmp(&a.rssi));
128        Ok(networks)
129    }
130
131    async fn connect(&self, network_config: NetworkConfig) -> Result<(), Error> {
132        let (client_controller, client_state_updates_request) = (self.get_client_controller)()?;
133
134        let network_id = network_config.id.clone().unwrap();
135
136        match client_controller.save_network(&network_config).await? {
137            Ok(()) => {
138                let result = client_controller.connect(&network_id).await?;
139                if result == wlan_policy::RequestStatus::Acknowledged {
140                    Ok(())
141                } else {
142                    Err(format_err!("Unexpected return from connect: {:?}", result))
143                }
144            }
145            Err(e) => Err(format_err!("failed to save network with {:?}", e)),
146        }?;
147        self.wait_for_connection(client_state_updates_request)
148            .on_timeout(MonotonicInstant::after(CONNECT_TIMEOUT), || {
149                bail!("Timed out waiting for wlan connection")
150            })
151            .await
152    }
153}
154
155/// Issues a scan request to the client policy layer.
156/// This function is largly copied from https://source.corp.google.com/fuchsia/src/connectivity/wlan/wlancfg/tool/policy/src/lib.rs;l=372?q=fn%20handle_scan&sq=package:fuchsia
157/// The match statement has been simplified.
158async fn handle_scan(
159    client_controller: wlan_policy::ClientControllerProxy,
160) -> Result<Vec<wlan_policy::ScanResult>, Error> {
161    let (client_proxy, server_end) = create_proxy::<wlan_policy::ScanResultIteratorMarker>();
162    client_controller.scan_for_networks(server_end)?;
163
164    let mut scanned_networks = Vec::<wlan_policy::ScanResult>::new();
165    loop {
166        match client_proxy.get_next().await? {
167            Ok(mut new_networks) => {
168                if new_networks.is_empty() {
169                    break;
170                }
171                scanned_networks.append(&mut new_networks);
172            }
173            Err(e) => return Err(format_err!("Scan failure error: {:?}", e)),
174        }
175    }
176
177    Ok(scanned_networks)
178}
179
180#[cfg(test)]
181mod tests {
182    use assert_matches::assert_matches;
183    use fuchsia_async as fasync;
184    use std::cell::Cell;
185    use std::future::Future;
186
187    use super::*;
188
189    fn mock_wlan_policy<H, F>(handler: H) -> Result<Box<GetClientController>, Error>
190    where
191        F: 'static + Future<Output = ()>,
192        H: Fn(
193            wlan_policy::ClientControllerRequestStream,
194            wlan_policy::ClientStateUpdatesProxy,
195        ) -> F,
196    {
197        // Create FIDL endpoints for ClientController and ClientStateUpdates protocols.
198        let (client_controller_proxy, client_controller_request_stream) =
199            fidl::endpoints::create_proxy_and_stream::<wlan_policy::ClientControllerMarker>();
200        let (client_state_updates_proxy, client_state_updates_request_stream) =
201            fidl::endpoints::create_proxy_and_stream::<wlan_policy::ClientStateUpdatesMarker>();
202
203        // Spawn handler.
204        fasync::Task::local(handler(client_controller_request_stream, client_state_updates_proxy))
205            .detach();
206
207        // Make a function that will return the ClientControllerProxy and
208        // ClientStateUpdatesRequestStream exactly once.
209        let gcc_return =
210            Cell::new(Some((client_controller_proxy, client_state_updates_request_stream)));
211        let gcc = Box::new(move || Ok(gcc_return.replace(None).unwrap()));
212
213        Ok(gcc)
214    }
215
216    #[fasync::run_until_stalled(test)]
217    async fn connect() {
218        fn network_id() -> wlan_policy::NetworkIdentifier {
219            wlan_policy::NetworkIdentifier {
220                ssid: vec![64, 64, 64, 64],
221                type_: wlan_policy::SecurityType::Wpa2,
222            }
223        }
224        fn network_config() -> wlan_policy::NetworkConfig {
225            wlan_policy::NetworkConfig {
226                id: Some(network_id()),
227                credential: Some(wlan_policy::Credential::Password(vec![66, 66, 66, 66])),
228                ..Default::default()
229            }
230        }
231
232        let get_client_controller = mock_wlan_policy(|mut request_stream, proxy| async move {
233            use wlan_policy::ClientControllerRequest::*;
234
235            while let Some(request) = request_stream.try_next().await.unwrap() {
236                match request {
237                    SaveNetwork { config, responder } => {
238                        assert_eq!(config, network_config());
239                        responder.send(Ok(())).unwrap();
240                    }
241                    Connect { id, responder } => {
242                        assert_eq!(id, network_id());
243                        responder.send(wlan_policy::RequestStatus::Acknowledged).unwrap();
244
245                        proxy
246                            .on_client_state_update(&wlan_policy::ClientStateSummary {
247                                state: Some(wlan_policy::WlanClientState::ConnectionsEnabled),
248                                networks: Some(vec![wlan_policy::NetworkState {
249                                    id: Some(network_id()),
250                                    state: Some(wlan_policy::ConnectionState::Connecting),
251                                    status: None,
252                                    ..Default::default()
253                                }]),
254                                ..Default::default()
255                            })
256                            .await
257                            .expect("sending client state update");
258                        proxy
259                            .on_client_state_update(&wlan_policy::ClientStateSummary {
260                                state: Some(wlan_policy::WlanClientState::ConnectionsEnabled),
261                                networks: Some(vec![wlan_policy::NetworkState {
262                                    id: Some(network_id()),
263                                    state: Some(wlan_policy::ConnectionState::Connected),
264                                    status: None,
265                                    ..Default::default()
266                                }]),
267                                ..Default::default()
268                            })
269                            .await
270                            .expect("sending client state update");
271                    }
272                    _ => unimplemented!(),
273                }
274            }
275        })
276        .expect("create mock");
277        let wci = WifiConnectImpl { get_client_controller };
278        wci.connect(network_config()).await.expect("connect");
279    }
280
281    #[test]
282    fn connect_timeout() {
283        // Fake time to test the timeout.
284        let mut exec = fasync::TestExecutor::new_with_fake_time();
285        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
286
287        fn network_id() -> wlan_policy::NetworkIdentifier {
288            wlan_policy::NetworkIdentifier {
289                ssid: vec![64, 64, 64, 64],
290                type_: wlan_policy::SecurityType::Wpa2,
291            }
292        }
293        fn network_config() -> wlan_policy::NetworkConfig {
294            wlan_policy::NetworkConfig {
295                id: Some(network_id()),
296                credential: Some(wlan_policy::Credential::Password(vec![66, 66, 66, 66])),
297                ..Default::default()
298            }
299        }
300
301        let get_client_controller = mock_wlan_policy(|mut request_stream, proxy| async move {
302            use wlan_policy::ClientControllerRequest::*;
303
304            while let Some(request) = request_stream.try_next().await.unwrap() {
305                match request {
306                    SaveNetwork { config, responder } => {
307                        assert_eq!(config, network_config());
308                        responder.send(Ok(())).unwrap();
309                    }
310                    Connect { id, responder } => {
311                        assert_eq!(id, network_id());
312                        responder.send(wlan_policy::RequestStatus::Acknowledged).unwrap();
313
314                        proxy
315                            .on_client_state_update(&wlan_policy::ClientStateSummary {
316                                state: Some(wlan_policy::WlanClientState::ConnectionsEnabled),
317                                networks: Some(vec![wlan_policy::NetworkState {
318                                    id: Some(network_id()),
319                                    state: Some(wlan_policy::ConnectionState::Connecting),
320                                    status: None,
321                                    ..Default::default()
322                                }]),
323                                ..Default::default()
324                            })
325                            .await
326                            .expect("sending client state update");
327                        proxy
328                            .on_client_state_update(&wlan_policy::ClientStateSummary {
329                                state: Some(wlan_policy::WlanClientState::ConnectionsEnabled),
330                                networks: Some(vec![wlan_policy::NetworkState {
331                                    id: Some(network_id()),
332                                    state: Some(wlan_policy::ConnectionState::Disconnected),
333                                    status: None,
334                                    ..Default::default()
335                                }]),
336                                ..Default::default()
337                            })
338                            .await
339                            .expect("sending client state update");
340                    }
341                    _ => unimplemented!(),
342                }
343            }
344        })
345        .expect("create mock");
346
347        let wci = WifiConnectImpl { get_client_controller };
348
349        let mut connect_future = Box::pin(wci.connect(network_config()));
350        // The future shouldn't complete before sleeping, waiting for a connection
351        assert!(exec.run_until_stalled(&mut connect_future).is_pending());
352        // .... the passage of time...
353        exec.wake_next_timer().unwrap();
354        assert_matches!(
355            exec.run_until_stalled(&mut connect_future),
356            futures::task::Poll::Ready(Err(_))
357        );
358    }
359}