blob: e360cae25a8d2449913f0d258c6b2af03d8dc2eb [file] [log] [blame]
// 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, known_ess_store::KnownEssStore};
use fidl::{self, endpoints::create_proxy};
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_device_service as wlan_service;
use fidl_fuchsia_wlan_service as legacy;
use fidl_fuchsia_wlan_sme as fidl_sme;
use fidl_fuchsia_wlan_stats as fidl_wlan_stats;
use fuchsia_zircon as zx;
use futures::{channel::oneshot, prelude::*};
use 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,
ess_store: Arc<KnownEssStore>,
) -> Result<(), fidl::Error> {
await!(requests.try_for_each_concurrent(MAX_CONCURRENT_WLAN_REQUESTS, |req| handle_request(
&client,
req,
Arc::clone(&ess_store)
)))
}
async fn handle_request(
client: &ClientRef,
req: legacy::WlanRequest,
ess_store: Arc<KnownEssStore>,
) -> 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)
}
legacy::WlanRequest::ClearSavedNetworks { responder } => {
if let Err(e) = ess_store.clear() {
eprintln!("Error clearing known ESS: {}", e);
}
responder.send()
}
}
}
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,
scan_type: fidl_common::ScanType::Passive,
};
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_common::WlanChan {
primary: bss.channel,
secondary80: 0,
cbw: fidl_common::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() }
}