blob: 533eff9fe556861c69541476ffaf5c50352309d7 [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;
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(),
}
}