1use 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 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
155async 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 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 fasync::Task::local(handler(client_controller_request_stream, client_state_updates_proxy))
205 .detach();
206
207 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 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 assert!(exec.run_until_stalled(&mut connect_future).is_pending());
352 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}