| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| common_utils::common::macros::with_line, |
| wlan_policy::types::{ClientStateSummary, NetworkConfig}, |
| }, |
| anyhow::{format_err, Context as _, Error}, |
| fidl::endpoints::Proxy as _, |
| fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_policy as fidl_policy, |
| fuchsia_async::{self as fasync, DurationExt as _}, |
| fuchsia_component::client::connect_to_protocol, |
| fuchsia_sync::RwLock, |
| fuchsia_zircon as zx, |
| futures::TryStreamExt, |
| std::{ |
| cell::Cell, |
| collections::HashSet, |
| fmt::{self, Debug}, |
| }, |
| tracing::*, |
| }; |
| |
| pub struct WlanPolicyFacade { |
| controller: RwLock<InnerController>, |
| update_listener: Cell<Option<fidl_policy::ClientStateUpdatesRequestStream>>, |
| } |
| |
| #[derive(Debug)] |
| pub struct InnerController { |
| inner: Option<fidl_policy::ClientControllerProxy>, |
| } |
| |
| impl Debug for WlanPolicyFacade { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let listener = self.update_listener.take(); |
| let update_listener = |
| if listener.is_some() { "Some(ClientStateUpdatesRequestStream)" } else { "None" } |
| .to_string(); |
| self.update_listener.set(listener); |
| |
| f.debug_struct("InnerWlanPolicyFacade") |
| .field("controller", &self.controller) |
| .field("update_listener", &update_listener) |
| .finish() |
| } |
| } |
| |
| impl WlanPolicyFacade { |
| pub fn new() -> Result<WlanPolicyFacade, Error> { |
| Ok(Self { |
| controller: RwLock::new(InnerController { inner: None }), |
| update_listener: Cell::new(None), |
| }) |
| } |
| |
| /// Create a client controller and listen for client state updates. If the facade already has |
| /// a client controller, recreate it and start listening for client state updates again. |
| /// See [`WlanPolicyFacade::get_update()`] for details about listening for updates. |
| /// |
| /// A client controller is necessary to access the fuchsia.wlan.policy.ClientController API. |
| /// Only one caller can have the control channel open at a time. |
| pub async fn create_client_controller(&self) -> Result<(), Error> { |
| let tag = "WlanPolicyFacade::create_client_controller"; |
| let mut controller_guard = self.controller.write(); |
| // Drop the controller if the facade has one, otherwise creating a controller will fail. |
| controller_guard.inner = None; |
| |
| let (controller, update_stream) = Self::init_client_controller().await.map_err(|e| { |
| info!(tag = &with_line!(tag), "Error getting client controller: {}", e); |
| format_err!("Error getting client controller: {}", e) |
| })?; |
| controller_guard.inner = Some(controller); |
| self.update_listener.set(Some(update_stream)); |
| |
| Ok(()) |
| } |
| |
| /// Creates and returns a client controller. This also returns the stream for listener updates |
| /// that is created in the process of creating the client controller. |
| async fn init_client_controller() -> Result< |
| (fidl_policy::ClientControllerProxy, fidl_policy::ClientStateUpdatesRequestStream), |
| Error, |
| > { |
| let provider = connect_to_protocol::<fidl_policy::ClientProviderMarker>()?; |
| let (controller, req) = |
| fidl::endpoints::create_proxy::<fidl_policy::ClientControllerMarker>()?; |
| let (update_sink, update_stream) = |
| fidl::endpoints::create_request_stream::<fidl_policy::ClientStateUpdatesMarker>()?; |
| provider.get_controller(req, update_sink)?; |
| |
| // Sleep very briefly to introduce a yield point (with the await) so that in case the other |
| // end of the channel is closed, its status is correctly propagated by the kernel and we can |
| // accurately check it using `is_closed()`. |
| let sleep_duration = zx::Duration::from_millis(10); |
| fasync::Timer::new(sleep_duration.after_now()).await; |
| if controller.is_closed() { |
| return Err(format_err!( |
| "Policy layer closed channel, client controller is likely already in use." |
| )); |
| } |
| |
| Ok((controller, update_stream)) |
| } |
| |
| /// Drop the facade's client controller so that something else can get a controller. |
| pub fn drop_client_controller(&self) { |
| let mut controller_guard = self.controller.write(); |
| controller_guard.inner = None; |
| } |
| |
| /// Creates a listener update stream for getting status updates. |
| fn init_listener() -> Result<fidl_policy::ClientStateUpdatesRequestStream, Error> { |
| let listener = connect_to_protocol::<fidl_policy::ClientListenerMarker>()?; |
| let (client_end, server_end) = |
| fidl::endpoints::create_endpoints::<fidl_policy::ClientStateUpdatesMarker>(); |
| listener.get_listener(client_end)?; |
| Ok(server_end.into_stream()?) |
| } |
| |
| /// This function will set a new listener even if there is one because new listeners will get |
| /// the most recent update immediately without waiting. This might be used to set the facade's |
| /// listener update stream if it wasn't set by creating the client controller or to set a clean |
| /// state for a new test. |
| pub fn set_new_listener(&self) -> Result<(), Error> { |
| self.update_listener.set(Some(Self::init_listener()?)); |
| Ok(()) |
| } |
| |
| /// Request a scan and return the list of network names found, or an error if one occurs. |
| pub async fn scan_for_networks(&self) -> Result<Vec<String>, Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| // Policy will send results back through this iterator |
| let (iter, server) = |
| fidl::endpoints::create_proxy::<fidl_policy::ScanResultIteratorMarker>() |
| .context("failed to create iterator")?; |
| // Request a scan from policy |
| controller.scan_for_networks(server)?; |
| |
| // Get results and check for scan error. Get the next chunk of results until we get an |
| // error or empty list, which indicates the end of results. |
| let mut scan_results = HashSet::new(); |
| loop { |
| let results = iter.get_next().await?.map_err(|e| format_err!("{:?}", e))?; |
| if results.is_empty() { |
| break; |
| } |
| |
| // For now, just return the names of the scanned networks. |
| let results = Self::stringify_scan_results(results); |
| scan_results.extend(results); |
| } |
| Ok(scan_results.into_iter().collect()) |
| } |
| |
| /// Connect to a network through the policy layer. The network must have been saved first. |
| /// Returns an error if the connect command was not received, otherwise returns the response |
| /// to the connect request as a string. A connection should be triggered if the response is |
| /// "Acknowledged". |
| /// # Arguments: |
| /// * `target_ssid': The SSID (network name) that we want to connect to. |
| /// * `type`: Security type should be a string of the security type, either "none", "wep", |
| /// "wpa", "wpa2" or "wpa3", matching the policy API's defined security types, case |
| /// doesn't matter. |
| pub async fn connect( |
| &self, |
| target_ssid: Vec<u8>, |
| type_: fidl_policy::SecurityType, |
| ) -> Result<String, Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| let network_id = fidl_policy::NetworkIdentifier { ssid: target_ssid, type_ }; |
| let response = controller |
| .connect(&network_id) |
| .await |
| .map_err(|e| format_err!("Connect: failed to connect: {}", e))?; |
| Ok(Self::request_status_as_string(response)) |
| } |
| |
| fn request_status_as_string(response: fidl_common::RequestStatus) -> String { |
| match response { |
| fidl_common::RequestStatus::Acknowledged => "Acknowledged", |
| fidl_common::RequestStatus::RejectedNotSupported => "RejectedNotSupported", |
| fidl_common::RequestStatus::RejectedIncompatibleMode => "RejectedIncompatibleMode", |
| fidl_common::RequestStatus::RejectedAlreadyInUse => "RejectedAlreadyInUse", |
| fidl_common::RequestStatus::RejectedDuplicateRequest => "RejectedDuplicateRequest", |
| } |
| .to_string() |
| } |
| |
| /// Forget the specified saved network. Doesn't do anything if network not saved. |
| /// # Arguments: |
| /// * `target_ssid`: The SSID (network name) that we want to forget. |
| /// * `type`: the security type of the network. It should be a string, either "none", "wep", |
| /// "wpa", "wpa2" or "wpa3", matching the policy API's defined security types. Target |
| /// password can be password, PSK, or none, represented by empty string. |
| /// * `credential`: the password or other credential of the network we want to forget. |
| pub async fn remove_network( |
| &self, |
| target_ssid: Vec<u8>, |
| type_: fidl_policy::SecurityType, |
| credential: fidl_policy::Credential, |
| ) -> Result<(), Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| info!( |
| tag = &with_line!("WlanPolicyFacade::remove_network"), |
| "Removing network: ({}{:?})", |
| String::from_utf8_lossy(&target_ssid), |
| type_ |
| ); |
| |
| let config = fidl_policy::NetworkConfig { |
| id: Some(fidl_policy::NetworkIdentifier { ssid: target_ssid, type_ }), |
| credential: Some(credential), |
| ..Default::default() |
| }; |
| controller |
| .remove_network(&config) |
| .await |
| .map_err(|err| format_err!("{:?}", err))? // FIDL error |
| .map_err(|err| format_err!("{:?}", err)) // network config change error |
| } |
| |
| /// Remove all of the client's saved networks. |
| pub async fn remove_all_networks(&self) -> Result<(), Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| // Remove each saved network individually. |
| let saved_networks = self.get_saved_networks().await?; |
| for network_config in saved_networks { |
| controller |
| .remove_network(&network_config) |
| .await |
| .map_err(|err| format_err!("{:?}", err))? // FIDL error |
| .map_err(|err| format_err!("{:?}", err))?; // network config change error |
| } |
| Ok(()) |
| } |
| |
| /// Send the request to the policy layer to start making client connections. |
| pub async fn start_client_connections(&self) -> Result<(), Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| let req_status = controller.start_client_connections().await?; |
| if fidl_common::RequestStatus::Acknowledged == req_status { |
| Ok(()) |
| } else { |
| bail!("{:?}", req_status); |
| } |
| } |
| |
| /// Wait for and return a client update. If this is the first update gotten from the facade |
| /// since the client controller or a new update listener has been created, it will get an |
| /// immediate status. After that, it will wait for a change and return a status when there has |
| /// been a change since the last call to get_update. This call will hang if there are no |
| /// updates. |
| /// This function is not thread safe, so there should not be multiple get_update calls at the |
| /// same time unless a new listener is set between them. There is no lock around the |
| /// update_listener field of the facade in order to prevent a hanging get_update from blocking |
| /// all future get_updates. |
| pub async fn get_update(&self) -> Result<ClientStateSummary, Error> { |
| // Initialize the update listener if it has not been initialized. |
| let listener = self.update_listener.take(); |
| let mut update_listener = if listener.is_none() { |
| Self::init_listener() |
| } else { |
| listener.ok_or(format_err!("failed to set update listener of facade")) |
| }?; |
| |
| if let Some(update_request) = update_listener.try_next().await? { |
| let update = update_request.into_on_client_state_update(); |
| let (update, responder) = match update { |
| Some((update, responder)) => (update, responder), |
| None => return Err(format_err!("Client provider produced invalid update.")), |
| }; |
| // Ack the update. |
| responder.send().map_err(|e| format_err!("failed to ack update: {}", e))?; |
| // Put the update listener back in the facade |
| self.update_listener.set(Some(update_listener)); |
| Ok(update.into()) |
| } else { |
| self.update_listener.set(Some(update_listener)); |
| Err(format_err!("update listener's next update is None")) |
| } |
| } |
| |
| /// Send the request to the policy layer to stop making client connections. |
| pub async fn stop_client_connections(&self) -> Result<(), Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| let req_status = controller.stop_client_connections().await?; |
| if fidl_common::RequestStatus::Acknowledged == req_status { |
| Ok(()) |
| } else { |
| bail!("{:?}", req_status); |
| } |
| } |
| |
| /// Save the specified network. |
| /// # Arguments: |
| /// * `target_ssid`: The SSID (network name) that we want to save. |
| /// * `type`: the security type of the network. It should be a string, either "none", "wep", |
| /// "wpa", "wpa2" or "wpa3", matching the policy API's defined security types. Target |
| /// password can be password, PSK, or none, represented by empty string |
| /// * `credential`: the password or other credential of the network we want to remember. |
| pub async fn save_network( |
| &self, |
| target_ssid: Vec<u8>, |
| type_: fidl_policy::SecurityType, |
| credential: fidl_policy::Credential, |
| ) -> Result<(), Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| let network_id = fidl_policy::NetworkIdentifier { ssid: target_ssid.clone(), type_: type_ }; |
| |
| controller |
| .save_network(&fidl_policy::NetworkConfig { |
| id: Some(network_id), |
| credential: Some(credential), |
| ..Default::default() |
| }) |
| .await? |
| .map_err(|e| format_err!("{:?}", e)) |
| } |
| |
| pub async fn get_saved_networks_json(&self) -> Result<Vec<NetworkConfig>, Error> { |
| let saved_networks = self.get_saved_networks().await?; |
| // Convert FIDL network configs to JSON values that can be passed through SL4F |
| Ok(saved_networks.into_iter().map(|cfg| cfg.into()).collect::<Vec<_>>()) |
| } |
| |
| /// Get a list of the saved networks. Returns FIDL values to be used directly or converted to |
| /// serializable values that can be passed through SL4F |
| async fn get_saved_networks(&self) -> Result<Vec<fidl_policy::NetworkConfig>, Error> { |
| let controller_guard = self.controller.read(); |
| let controller = controller_guard |
| .inner |
| .as_ref() |
| .ok_or(format_err!("client controller has not been initialized"))?; |
| |
| // Policy will send configs back through this iterator |
| let (iter, server) = |
| fidl::endpoints::create_proxy::<fidl_policy::NetworkConfigIteratorMarker>() |
| .context("failed to create iterator")?; |
| controller |
| .get_saved_networks(server) |
| .map_err(|e| format_err!("Get saved networks: fidl error {:?}", e))?; |
| |
| // get each config from the stream as they become available |
| let mut networks = vec![]; |
| loop { |
| let cfgs = iter.get_next().await?; |
| if cfgs.is_empty() { |
| break; |
| } |
| networks.extend(cfgs); |
| } |
| Ok(networks) |
| } |
| |
| fn stringify_scan_results(results: Vec<fidl_policy::ScanResult>) -> Vec<String> { |
| results |
| .into_iter() |
| .filter_map(|result| result.id) |
| .map(|id| String::from_utf8_lossy(&id.ssid).into_owned()) |
| .collect() |
| } |
| } |