| // Copyright 2018 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::client; |
| |
| use { |
| fidl_fuchsia_wlan_device_service as wlan_service, |
| fidl_fuchsia_wlan_mlme as fidl_mlme, |
| fidl_fuchsia_wlan_service as legacy, |
| fidl_fuchsia_wlan_sme as fidl_sme, |
| fidl_fuchsia_wlan_stats as fidl_wlan_stats, |
| fidl::{self, endpoints::create_proxy}, |
| fuchsia_zircon as zx, |
| futures::{prelude::*, channel::oneshot}, |
| std::sync::{Arc, Mutex}, |
| }; |
| |
| #[derive(Clone)] |
| pub struct Client { |
| pub service: wlan_service::DeviceServiceProxy, |
| pub client: client::Client, |
| pub sme: fidl_sme::ClientSmeProxy, |
| pub iface_id: u16 |
| } |
| |
| #[derive(Clone)] |
| pub struct ClientRef(Arc<Mutex<Option<Client>>>); |
| |
| impl ClientRef { |
| pub fn new() -> Self { |
| ClientRef(Arc::new(Mutex::new(None))) |
| } |
| |
| pub fn set_if_empty(&self, client: Client) { |
| let mut c = self.0.lock().unwrap(); |
| if c.is_none() { |
| *c = Some(client); |
| } |
| } |
| |
| pub fn remove_if_matching(&self, iface_id: u16) { |
| let mut c = self.0.lock().unwrap(); |
| let same_id = match *c { |
| Some(ref c) => c.iface_id == iface_id, |
| None => false |
| }; |
| if same_id { |
| *c = None; |
| } |
| } |
| |
| pub fn get(&self) -> Result<Client, legacy::Error> { |
| self.0.lock().unwrap().clone().ok_or_else(|| |
| legacy::Error { |
| code: legacy::ErrCode::NotFound, |
| description: "No wireless interface found".to_string(), |
| }) |
| } |
| } |
| |
| const MAX_CONCURRENT_WLAN_REQUESTS: usize = 1000; |
| |
| pub async fn serve_legacy(requests: legacy::WlanRequestStream, client: ClientRef) |
| -> Result<(), fidl::Error> |
| { |
| await!(requests.try_for_each_concurrent(MAX_CONCURRENT_WLAN_REQUESTS, |
| |req| handle_request(&client, req))) |
| } |
| |
| async fn handle_request(client: &ClientRef, req: legacy::WlanRequest) -> Result<(), fidl::Error> { |
| match req { |
| legacy::WlanRequest::Scan { req, responder } => { |
| let mut r = await!(scan(client, req)); |
| responder.send(&mut r) |
| }, |
| legacy::WlanRequest::Connect { req, responder } => { |
| let mut r = await!(connect(&client, req)); |
| responder.send(&mut r) |
| }, |
| legacy::WlanRequest::Disconnect { responder } => { |
| let mut r = await!(disconnect(client.clone())); |
| responder.send(&mut r) |
| }, |
| legacy::WlanRequest::Status { responder } => { |
| let mut r = await!(status(&client)); |
| responder.send(&mut r) |
| }, |
| legacy::WlanRequest::StartBss { responder, .. } => { |
| eprintln!("StartBss() is not implemented"); |
| responder.send(&mut not_supported()) |
| }, |
| legacy::WlanRequest::StopBss { responder } => { |
| eprintln!("StopBss() is not implemented"); |
| responder.send(&mut not_supported()) |
| }, |
| legacy::WlanRequest::Stats { responder } => { |
| let mut r = await!(stats(client)); |
| responder.send(&mut r) |
| }, |
| } |
| } |
| |
| async fn scan<'a>(client: &ClientRef, legacy_req: legacy::ScanRequest) |
| -> legacy::ScanResult |
| { |
| let r = await!(async move { |
| let client = client.get()?; |
| let scan_txn = start_scan_txn(&client, legacy_req) |
| .map_err(|e| { |
| eprintln!("Failed to start a scan transaction: {}", e); |
| internal_error() |
| })?; |
| |
| let mut evt_stream = scan_txn.take_event_stream(); |
| let mut aps = vec![]; |
| let mut done = false; |
| |
| while let Some(event) = await!(evt_stream.try_next()) |
| .map_err(|e| { |
| eprintln!("Error reading from scan transaction stream: {}", e); |
| internal_error() |
| })? |
| { |
| match event { |
| fidl_sme::ScanTransactionEvent::OnResult { aps: new_aps } => { |
| aps.extend(new_aps); |
| done = false; |
| }, |
| fidl_sme::ScanTransactionEvent::OnFinished { } => done = true, |
| fidl_sme::ScanTransactionEvent::OnError { error } => |
| return Err(convert_scan_err(error)), |
| } |
| } |
| |
| if !done { |
| eprintln!("Failed to fetch all results before the channel was closed"); |
| return Err(internal_error()); |
| } |
| |
| Ok(aps.into_iter().map(|ess| convert_bss_info(ess.best_bss)).collect()) |
| }); |
| |
| match r { |
| Ok(aps) => legacy::ScanResult { error: success(), aps: Some(aps) }, |
| Err(error) => legacy::ScanResult { error, aps: None }, |
| } |
| } |
| |
| fn start_scan_txn(client: &Client, legacy_req: legacy::ScanRequest) |
| -> Result<fidl_sme::ScanTransactionProxy, fidl::Error> |
| { |
| let (scan_txn, remote) = create_proxy()?; |
| let mut req = fidl_sme::ScanRequest { |
| timeout: legacy_req.timeout |
| }; |
| client.sme.scan(&mut req, remote)?; |
| Ok(scan_txn) |
| } |
| |
| fn convert_scan_err(error: fidl_sme::ScanError) -> legacy::Error { |
| legacy::Error { |
| code: match error.code { |
| fidl_sme::ScanErrorCode::NotSupported => legacy::ErrCode::NotSupported, |
| fidl_sme::ScanErrorCode::InternalError => legacy::ErrCode::Internal, |
| }, |
| description: error.message |
| } |
| } |
| |
| fn convert_bss_info(bss: fidl_sme::BssInfo) -> legacy::Ap { |
| legacy::Ap { |
| bssid: bss.bssid.to_vec(), |
| ssid: String::from_utf8_lossy(&bss.ssid).to_string(), |
| rssi_dbm: bss.rx_dbm, |
| is_secure: bss.protected, |
| is_compatible: bss.compatible, |
| chan: fidl_mlme::WlanChan { |
| primary: bss.channel, |
| secondary80: 0, |
| cbw: fidl_mlme::Cbw::Cbw20 |
| } |
| } |
| } |
| |
| fn connect(client: &ClientRef, legacy_req: legacy::ConnectConfig) |
| -> impl Future<Output = legacy::Error> |
| { |
| future::ready(client.get()) |
| .and_then(move |client| { |
| let (responder, receiver) = oneshot::channel(); |
| let req = client::ConnectRequest { |
| ssid: legacy_req.ssid.as_bytes().to_vec(), |
| password: legacy_req.pass_phrase.as_bytes().to_vec(), |
| responder |
| }; |
| future::ready(client.client.connect(req) |
| .map_err(|e| { |
| eprintln!("Failed to start a connect transaction: {}", e); |
| internal_error() |
| })) |
| .and_then(move |()| { |
| receiver.map_err(|_e| { |
| eprintln!("Did not receive a connect result"); |
| internal_error() |
| }) |
| }) |
| }) |
| .map_ok(convert_connect_result) |
| .unwrap_or_else(|e| e) |
| } |
| |
| async fn disconnect(client: ClientRef) -> legacy::Error { |
| let client = match client.get() { |
| Ok(c) => c, |
| Err(e) => return e, |
| }; |
| let (responder, receiver) = oneshot::channel(); |
| if let Err(e) = client.client.disconnect(responder) { |
| eprintln!("Failed to enqueue a disconnect command: {}", e); |
| return internal_error(); |
| } |
| match await!(receiver) { |
| Ok(()) => success(), |
| Err(_) => error_message("Request was canceled"), |
| } |
| } |
| |
| fn convert_connect_result(code: fidl_sme::ConnectResultCode) -> legacy::Error { |
| match code { |
| fidl_sme::ConnectResultCode::Success => success(), |
| fidl_sme::ConnectResultCode::Canceled |
| => error_message("Request was canceled"), |
| fidl_sme::ConnectResultCode::BadCredentials |
| => error_message("Failed to join; bad credentials"), |
| fidl_sme::ConnectResultCode::Failed |
| => error_message("Failed to join") |
| } |
| } |
| |
| fn status(client: &ClientRef) |
| -> impl Future<Output = legacy::WlanStatus> |
| { |
| future::ready(client.get()) |
| .and_then(|client| { |
| client.sme.status() |
| .map_err(|e| { |
| eprintln!("Failed to query status: {}", e); |
| internal_error() |
| }) |
| }) |
| .map(|r| match r { |
| Ok(status) => legacy::WlanStatus { |
| error: success(), |
| state: convert_state(&status), |
| current_ap: status.connected_to.map(|bss| Box::new(convert_bss_info(*bss))), |
| }, |
| Err(error) => legacy::WlanStatus { |
| error, |
| state: legacy::State::Unknown, |
| current_ap: None |
| } |
| }) |
| } |
| |
| fn convert_state(status: &fidl_sme::ClientStatusResponse) -> legacy::State { |
| if status.connected_to.is_some() { |
| legacy::State::Associated |
| } else if !status.connecting_to_ssid.is_empty() { |
| legacy::State::Joining |
| } else { |
| // There is no "idle" or "disconnected" state in the legacy API |
| legacy::State::Querying |
| } |
| } |
| |
| fn stats(client: &ClientRef) |
| -> impl Future<Output = legacy::WlanStats> |
| { |
| future::ready(client.get()) |
| .and_then(|client| { |
| client.service.get_iface_stats(client.iface_id) |
| .map_err(|e| { |
| eprintln!("Failed to query statistics: {}", e); |
| internal_error() |
| }) |
| }) |
| .map(|r| match r { |
| Ok((zx::sys::ZX_OK, Some(iface_stats))) => legacy::WlanStats { |
| error: success(), |
| stats: *iface_stats, |
| }, |
| Ok((err_code, _)) => { |
| eprintln!("GetIfaceStats returned error code {}", zx::Status::from_raw(err_code)); |
| legacy::WlanStats { |
| error: internal_error(), |
| stats: empty_stats(), |
| } |
| }, |
| Err(error) => legacy::WlanStats { |
| error, |
| stats: empty_stats(), |
| } |
| }) |
| } |
| |
| fn internal_error() -> legacy::Error { |
| legacy::Error { |
| code: legacy::ErrCode::Internal, |
| description: "Internal error occurred".to_string(), |
| } |
| } |
| |
| fn success() -> legacy::Error { |
| legacy::Error { |
| code: legacy::ErrCode::Ok, |
| description: String::new() |
| } |
| } |
| |
| fn not_supported() -> legacy::Error { |
| legacy::Error { |
| code: legacy::ErrCode::NotSupported, |
| description: "Not supported".to_string() |
| } |
| } |
| |
| fn error_message(msg: &str) -> legacy::Error { |
| legacy::Error { |
| code: legacy::ErrCode::Internal, |
| description: msg.to_string(), |
| } |
| } |
| |
| fn empty_stats() -> fidl_wlan_stats::IfaceStats { |
| fidl_wlan_stats::IfaceStats { |
| dispatcher_stats: fidl_wlan_stats::DispatcherStats { |
| any_packet: empty_packet_counter(), |
| mgmt_frame: empty_packet_counter(), |
| ctrl_frame: empty_packet_counter(), |
| data_frame: empty_packet_counter(), |
| }, |
| mlme_stats: None |
| } |
| } |
| |
| fn empty_packet_counter() -> fidl_wlan_stats::PacketCounter { |
| fidl_wlan_stats::PacketCounter { |
| in_: empty_counter(), |
| out: empty_counter(), |
| drop: empty_counter(), |
| in_bytes: empty_counter(), |
| out_bytes: empty_counter(), |
| drop_bytes: empty_counter(), |
| } |
| } |
| |
| fn empty_counter() -> fidl_wlan_stats::Counter { |
| fidl_wlan_stats::Counter { |
| count: 0, |
| name: String::new(), |
| } |
| } |
| |