blob: 5232b5a365b3a45c1cc029765aa26f7448c6fdb7 [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 failure::bail;
use fidl::{endpoints::RequestStream, endpoints::ServerEnd};
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_mlme::{MlmeEventStream, MlmeProxy};
use fidl_fuchsia_wlan_sme::{self as fidl_sme, ClientSmeRequest};
use fuchsia_zircon as zx;
use futures::channel::mpsc;
use futures::{prelude::*, select, stream::FuturesUnordered};
use log::{error, info};
use pin_utils::pin_mut;
use std::marker::Unpin;
use std::sync::{Arc, Mutex};
use wlan_sme::client::{
BssInfo, ConnectResult, ConnectionAttemptId, DiscoveryError, EssDiscoveryResult, EssInfo,
InfoEvent, ScanTxnId,
};
use wlan_sme::{client as client_sme, DeviceInfo, InfoStream};
use crate::fidl_util::is_peer_closed;
use crate::stats_scheduler::StatsRequest;
use crate::telemetry;
use crate::Never;
use fuchsia_cobalt::CobaltSender;
pub type Endpoint = ServerEnd<fidl_sme::ClientSmeMarker>;
type Sme = client_sme::ClientSme;
struct ConnectionTimes {
att_id: ConnectionAttemptId,
txn_id: ScanTxnId,
connect_started_time: Option<zx::Time>,
scan_started_time: Option<zx::Time>,
assoc_started_time: Option<zx::Time>,
rsna_started_time: Option<zx::Time>,
}
pub async fn serve<S>(
proxy: MlmeProxy,
device_info: DeviceInfo,
event_stream: MlmeEventStream,
new_fidl_clients: mpsc::UnboundedReceiver<Endpoint>,
stats_requests: S,
cobalt_sender: CobaltSender,
) -> Result<(), failure::Error>
where
S: Stream<Item = StatsRequest> + Unpin,
{
let (sme, mlme_stream, info_stream, time_stream) = Sme::new(device_info);
let sme = Arc::new(Mutex::new(sme));
let mlme_sme = super::serve_mlme_sme(
proxy,
event_stream,
Arc::clone(&sme),
mlme_stream,
stats_requests,
time_stream,
);
let sme_fidl = serve_fidl(sme, new_fidl_clients, info_stream, cobalt_sender);
pin_mut!(mlme_sme);
pin_mut!(sme_fidl);
Ok(select! {
mlme_sme = mlme_sme.fuse() => mlme_sme?,
sme_fidl = sme_fidl.fuse() => sme_fidl?.into_any(),
})
}
async fn serve_fidl(
sme: Arc<Mutex<Sme>>,
new_fidl_clients: mpsc::UnboundedReceiver<Endpoint>,
info_stream: InfoStream,
mut cobalt_sender: CobaltSender,
) -> Result<Never, failure::Error> {
let mut new_fidl_clients = new_fidl_clients.fuse();
let mut info_stream = info_stream.fuse();
let mut fidl_clients = FuturesUnordered::new();
let mut connection_times = ConnectionTimes {
att_id: 0,
txn_id: 0,
connect_started_time: None,
scan_started_time: None,
assoc_started_time: None,
rsna_started_time: None,
};
loop {
select! {
info_event = info_stream.next() => match info_event {
Some(e) => handle_info_event(e, &mut cobalt_sender, &mut connection_times),
None => bail!("Info Event stream unexpectedly ended"),
},
new_fidl_client = new_fidl_clients.next() => match new_fidl_client {
Some(c) => fidl_clients.push(serve_fidl_endpoint(&sme, c)),
None => bail!("New FIDL client stream unexpectedly ended"),
},
() = fidl_clients.select_next_some() => {},
}
}
}
async fn serve_fidl_endpoint(sme: &Mutex<Sme>, endpoint: Endpoint) {
let stream = match endpoint.into_stream() {
Ok(s) => s,
Err(e) => {
error!("Failed to create a stream from a zircon channel: {}", e);
return;
}
};
const MAX_CONCURRENT_REQUESTS: usize = 1000;
let r = await!(stream.try_for_each_concurrent(MAX_CONCURRENT_REQUESTS, move |request| {
handle_fidl_request(sme, request)
}));
if let Err(e) = r {
error!("Error serving FIDL: {}", e);
return;
}
}
async fn handle_fidl_request(
sme: &Mutex<Sme>,
request: fidl_sme::ClientSmeRequest,
) -> Result<(), fidl::Error> {
match request {
ClientSmeRequest::Scan { req, txn, .. } => Ok(await!(scan(sme, txn, req.scan_type))
.unwrap_or_else(|e| error!("Error handling a scan transaction: {:?}", e))),
ClientSmeRequest::Connect { req, txn, .. } => Ok(await!(connect(sme, txn, req))
.unwrap_or_else(|e| error!("Error handling a connect transaction: {:?}", e))),
ClientSmeRequest::Disconnect { responder } => {
disconnect(sme);
responder.send()
}
ClientSmeRequest::Status { responder } => responder.send(&mut status(sme)),
}
}
async fn scan(
sme: &Mutex<Sme>,
txn: ServerEnd<fidl_sme::ScanTransactionMarker>,
scan_type: fidl_common::ScanType,
) -> Result<(), failure::Error> {
let handle = txn.into_stream()?.control_handle();
let receiver = sme.lock().unwrap().on_scan_command(scan_type);
let result = await!(receiver).unwrap_or(Err(DiscoveryError::InternalError));
let send_result = send_scan_results(handle, result);
filter_out_peer_closed(send_result)?;
Ok(())
}
async fn connect(
sme: &Mutex<Sme>,
txn: Option<ServerEnd<fidl_sme::ConnectTransactionMarker>>,
req: fidl_sme::ConnectRequest,
) -> Result<(), failure::Error> {
let handle = match txn {
None => None,
Some(txn) => Some(txn.into_stream()?.control_handle()),
};
let receiver = sme.lock().unwrap().on_connect_command(req);
let result = await!(receiver).unwrap_or(ConnectResult::Failed);
let send_result = send_connect_result(handle, result);
filter_out_peer_closed(send_result)?;
Ok(())
}
pub fn filter_out_peer_closed(r: Result<(), fidl::Error>) -> Result<(), fidl::Error> {
match r {
Err(ref e) if is_peer_closed(e) => Ok(()),
other => other,
}
}
fn disconnect(sme: &Mutex<Sme>) {
sme.lock().unwrap().on_disconnect_command();
}
fn status(sme: &Mutex<Sme>) -> fidl_sme::ClientStatusResponse {
let status = sme.lock().unwrap().status();
fidl_sme::ClientStatusResponse {
connected_to: status.connected_to.map(|bss| Box::new(convert_bss_info(bss))),
connecting_to_ssid: status.connecting_to.unwrap_or(Vec::new()),
}
}
fn id_mismatch(this: u64, other: u64, id_type: &str) -> bool {
if this != other {
info!("Found an event with different {} than expected: {}, {}", id_type, this, other);
return true;
}
return false;
}
fn handle_info_event(
e: InfoEvent,
cobalt_sender: &mut CobaltSender,
connection_times: &mut ConnectionTimes,
) {
match e {
InfoEvent::ConnectStarted => {
connection_times.connect_started_time = Some(zx::Time::get(zx::ClockId::Monotonic));
}
InfoEvent::ConnectFinished { result, failure } => {
if let Some(connect_started_time) = connection_times.connect_started_time {
let connection_finished_time = zx::Time::get(zx::ClockId::Monotonic);
telemetry::report_connection_delay(
cobalt_sender,
connect_started_time,
connection_finished_time,
&result,
&failure,
);
connection_times.connect_started_time = None;
}
}
InfoEvent::MlmeScanStart { txn_id } => {
connection_times.txn_id = txn_id;
connection_times.scan_started_time = Some(zx::Time::get(zx::ClockId::Monotonic));
}
InfoEvent::MlmeScanEnd { txn_id } => {
if id_mismatch(txn_id, connection_times.txn_id, "ScanTxnId") {
return;
}
if let Some(scan_started_time) = connection_times.scan_started_time {
let scan_finished_time = zx::Time::get(zx::ClockId::Monotonic);
telemetry::report_scan_delay(cobalt_sender, scan_started_time, scan_finished_time);
connection_times.scan_started_time = None;
}
}
InfoEvent::ScanDiscoveryFinished {
bss_count,
ess_count,
num_bss_by_standard,
num_bss_by_channel,
} => {
telemetry::report_neighbor_networks_count(cobalt_sender, bss_count, ess_count);
telemetry::report_standards(cobalt_sender, num_bss_by_standard);
telemetry::report_channels(cobalt_sender, num_bss_by_channel);
}
InfoEvent::AssociationStarted { att_id } => {
connection_times.att_id = att_id;
connection_times.assoc_started_time = Some(zx::Time::get(zx::ClockId::Monotonic));
}
InfoEvent::AssociationSuccess { att_id } => {
if id_mismatch(att_id, connection_times.att_id, "ConnectionAttemptId") {
return;
}
if let Some(assoc_started_time) = connection_times.assoc_started_time {
let assoc_finished_time = zx::Time::get(zx::ClockId::Monotonic);
telemetry::report_assoc_success_delay(
cobalt_sender,
assoc_started_time,
assoc_finished_time,
);
connection_times.assoc_started_time = None;
}
}
InfoEvent::RsnaStarted { att_id } => {
connection_times.att_id = att_id;
connection_times.rsna_started_time = Some(zx::Time::get(zx::ClockId::Monotonic));
}
InfoEvent::RsnaEstablished { att_id } => {
if id_mismatch(att_id, connection_times.att_id, "ConnectionAttemptId") {
return;
}
match connection_times.rsna_started_time {
None => info!("Received UserEvent.RsnaEstablished before UserEvent.RsnaStarted"),
Some(rsna_started_time) => {
let rsna_finished_time = zx::Time::get(zx::ClockId::Monotonic);
telemetry::report_rsna_established_delay(
cobalt_sender,
rsna_started_time,
rsna_finished_time,
);
connection_times.rsna_started_time = None;
}
}
}
}
}
fn send_scan_results(
handle: fidl_sme::ScanTransactionControlHandle,
result: EssDiscoveryResult,
) -> Result<(), fidl::Error> {
match result {
Ok(ess_list) => {
let mut fidl_list = ess_list.into_iter().map(convert_ess_info).collect::<Vec<_>>();
handle.send_on_result(&mut fidl_list.iter_mut())?;
handle.send_on_finished()?;
}
Err(e) => {
let mut fidl_err = fidl_sme::ScanError {
code: match e {
DiscoveryError::NotSupported => fidl_sme::ScanErrorCode::NotSupported,
DiscoveryError::InternalError => fidl_sme::ScanErrorCode::InternalError,
},
message: e.to_string(),
};
handle.send_on_error(&mut fidl_err)?;
}
}
Ok(())
}
fn convert_ess_info(ess: EssInfo) -> fidl_sme::EssInfo {
fidl_sme::EssInfo { best_bss: convert_bss_info(ess.best_bss) }
}
fn convert_bss_info(bss: BssInfo) -> fidl_sme::BssInfo {
fidl_sme::BssInfo {
bssid: bss.bssid,
ssid: bss.ssid,
rx_dbm: bss.rx_dbm,
channel: bss.channel,
protected: bss.protected,
compatible: bss.compatible,
}
}
fn send_connect_result(
handle: Option<fidl_sme::ConnectTransactionControlHandle>,
result: ConnectResult,
) -> Result<(), fidl::Error> {
if let Some(handle) = handle {
let code = match result {
ConnectResult::Success => fidl_sme::ConnectResultCode::Success,
ConnectResult::Canceled => fidl_sme::ConnectResultCode::Canceled,
ConnectResult::Failed => fidl_sme::ConnectResultCode::Failed,
ConnectResult::BadCredentials => fidl_sme::ConnectResultCode::BadCredentials,
};
handle.send_on_finished(code)?;
}
Ok(())
}