blob: af579c9e10b7a3b02261caedfeccf438c3cd2e26 [file] [log] [blame]
// Copyright 2022 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 {
anyhow::{format_err, Error},
fidl::endpoints::{ProtocolMarker, Proxy},
fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_sme as fidl_sme,
fidl_fuchsia_wlan_softmac as fidl_softmac,
fuchsia_async::{Duration, Task},
fuchsia_inspect::{Inspector, Node as InspectNode},
fuchsia_inspect_contrib::auto_persist,
fuchsia_zircon::{self as zx, HandleBased},
futures::{
channel::{
mpsc,
oneshot::{self, Canceled},
},
Future, FutureExt, StreamExt,
},
std::pin::Pin,
tracing::{error, info, warn},
wlan_fidl_ext::{ResponderExt, SendResultExt, WithName},
wlan_mlme::{
buffer::CBufferProvider,
device::{
completers::{InitCompleter, StopCompleter},
DeviceOps,
},
DriverEvent, DriverEventSink,
},
wlan_sme::serve::create_sme,
wlan_trace as wtrace,
};
const INSPECT_VMO_SIZE_BYTES: usize = 1000 * 1024;
pub struct WlanSoftmacHandle(DriverEventSink);
impl std::fmt::Debug for WlanSoftmacHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("WlanSoftmacHandle").finish()
}
}
impl WlanSoftmacHandle {
pub fn stop(mut self, stop_completer: StopCompleter) {
wtrace::duration!(c"WlanSoftmacHandle::stop");
let driver_event_sink = &mut self.0;
if let Err(e) = driver_event_sink.unbounded_send(DriverEvent::Stop(stop_completer)) {
error!("Failed to signal WlanSoftmac main loop thread to stop: {}", e);
if let DriverEvent::Stop(stop_completer) = e.into_inner() {
stop_completer.complete();
} else {
unreachable!();
}
}
driver_event_sink.disconnect();
}
}
/// Run the Rust portion of wlansoftmac which includes the following three futures:
///
/// - WlanSoftmacIfcBridge server
/// - MLME server
/// - SME server
///
/// The WlanSoftmacIfcBridge server future executes on a parallel thread because otherwise
/// synchronous calls from the MLME server into the vendor driver could deadlock if the vendor
/// driver calls a WlanSoftmacIfcBridge method before returning from a synchronous call. For
/// example, when the MLME server synchronously calls WlanSoftmac.StartActiveScan(), the vendor
/// driver may call WlanSoftmacIfc.NotifyScanComplete() before returning from
/// WlanSoftmac.StartActiveScan(). This can occur when the scan request results in immediate
/// cancellation despite the request having valid arguments.
///
/// This function calls init_completer() when either MLME initialization completes successfully or
/// an error occurs before MLME initialization completes. The Ok() value passed by init_completer()
/// is a WlanSoftmacHandle which contains the FFI for calling WlanSoftmacIfc methods from the C++
/// portion of wlansoftmac.
///
/// The return value of this function is distinct from the value passed in init_completer(). This
/// function returns in one of four cases:
///
/// - An error occurred during initialization.
/// - An error occurred while running.
/// - An error occurred during shutdown.
/// - Shutdown completed successfully.
///
/// Generally, errors during initializations will be returned immediately after this function calls
/// init_completer() with the same error. Later errors can be returned after this functoin calls
/// init_completer().
pub async fn start_and_serve<D: DeviceOps + Send + 'static>(
init_completer: impl FnOnce(Result<WlanSoftmacHandle, zx::Status>) + Send + 'static,
device: D,
buffer_provider: CBufferProvider,
) -> Result<(), zx::Status> {
wtrace::duration_begin_scope!(c"rust_driver::start_and_serve");
let (driver_event_sink, driver_event_stream) = DriverEventSink::new();
let softmac_handle_sink = driver_event_sink.clone();
let init_completer = InitCompleter::new(move |result: Result<(), zx::Status>| {
init_completer(result.map(|()| WlanSoftmacHandle(softmac_handle_sink)))
});
let (mlme_init_sender, mlme_init_receiver) = oneshot::channel();
let StartedDriver { softmac_ifc_bridge_request_stream, mlme, sme } = match start(
mlme_init_sender,
driver_event_sink.clone(),
driver_event_stream,
device,
buffer_provider,
)
.await
{
Err(status) => {
init_completer.complete(Err(status));
return Err(status);
}
Ok(x) => x,
};
serve(
init_completer,
mlme_init_receiver,
driver_event_sink,
softmac_ifc_bridge_request_stream,
mlme,
sme,
)
.await
}
struct StartedDriver<Mlme, Sme> {
pub softmac_ifc_bridge_request_stream: fidl_softmac::WlanSoftmacIfcBridgeRequestStream,
pub mlme: Mlme,
pub sme: Sme,
}
/// Start the bridged wlansoftmac driver by creating components to run two futures:
///
/// - MLME server
/// - SME server
///
/// This function will use the provided |device| to make various calls into the vendor driver
/// necessary to configure and create the components to run the futures.
async fn start<D: DeviceOps + Send + 'static>(
mlme_init_sender: oneshot::Sender<Result<(), zx::Status>>,
driver_event_sink: DriverEventSink,
driver_event_stream: mpsc::UnboundedReceiver<DriverEvent>,
mut device: D,
buffer_provider: CBufferProvider,
) -> Result<
StartedDriver<
Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>,
Pin<Box<impl Future<Output = Result<(), Error>>>>,
>,
zx::Status,
> {
wtrace::duration!(c"rust_driver::start");
let (softmac_ifc_bridge_proxy, softmac_ifc_bridge_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_softmac::WlanSoftmacIfcBridgeMarker>()
.map_err(|e| {
// Failure to unwrap indicates a critical failure in the driver init thread.
error!(
"Failed to get {} server stream: {}",
fidl_softmac::WlanSoftmacIfcBridgeMarker::DEBUG_NAME,
e
);
zx::Status::INTERNAL
})?;
// Bootstrap USME
let BootstrappedGenericSme { generic_sme_request_stream, legacy_privacy_support, inspect_node } =
bootstrap_generic_sme(&mut device, driver_event_sink, softmac_ifc_bridge_proxy).await?;
// Make a series of queries to gather device information from the vendor driver.
let softmac_info = device.wlan_softmac_query_response()?;
let sta_addr = softmac_info.sta_addr;
let device_info = match wlan_mlme::mlme_device_info_from_softmac(softmac_info) {
Ok(info) => info,
Err(e) => {
error!("Failed to get MLME device info: {}", e);
return Err(zx::Status::INTERNAL);
}
};
let mac_sublayer_support = device.mac_sublayer_support()?;
let mac_implementation_type = &mac_sublayer_support.device.mac_implementation_type;
if *mac_implementation_type != fidl_common::MacImplementationType::Softmac {
error!("Wrong MAC implementation type: {:?}", mac_implementation_type);
return Err(zx::Status::INTERNAL);
}
let security_support = device.security_support()?;
let spectrum_management_support = device.spectrum_management_support()?;
// TODO(https://fxbug.dev/42064968): Get persistence working by adding the appropriate configs
// in *.cml files
let (persistence_proxy, _persistence_server_end) = match fidl::endpoints::create_proxy::<
fidl_fuchsia_diagnostics_persist::DataPersistenceMarker,
>() {
Ok(r) => r,
Err(e) => {
error!("Failed to create persistence proxy: {}", e);
return Err(zx::Status::INTERNAL);
}
};
let (persistence_req_sender, _persistence_req_forwarder_fut) =
auto_persist::create_persistence_req_sender(persistence_proxy);
let config = wlan_sme::Config {
wep_supported: legacy_privacy_support.wep_supported,
wpa1_supported: legacy_privacy_support.wpa1_supported,
};
// TODO(https://fxbug.dev/42077094): The MLME event stream should be moved out of DeviceOps
// entirely.
let mlme_event_stream = match device.take_mlme_event_stream() {
Some(mlme_event_stream) => mlme_event_stream,
None => {
error!("Failed to take MLME event stream.");
return Err(zx::Status::INTERNAL);
}
};
// Create an SME future to serve
let (mlme_request_stream, sme) = match create_sme(
config,
mlme_event_stream,
&device_info,
mac_sublayer_support,
security_support,
spectrum_management_support,
inspect_node,
persistence_req_sender,
generic_sme_request_stream,
) {
Ok((mlme_request_stream, sme)) => (mlme_request_stream, sme),
Err(e) => {
error!("Failed to create sme: {}", e);
return Err(zx::Status::INTERNAL);
}
};
// Create an MLME future to serve
let mlme: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>> = match device_info.role {
fidl_common::WlanMacRole::Client => {
info!("Running wlansoftmac with client role");
let config = wlan_mlme::client::ClientConfig {
ensure_on_channel_time: Duration::from_millis(500).into_nanos(),
};
Box::pin(wlan_mlme::mlme_main_loop::<wlan_mlme::client::ClientMlme<D>>(
mlme_init_sender,
config,
device,
buffer_provider,
mlme_request_stream,
driver_event_stream,
))
}
fidl_common::WlanMacRole::Ap => {
info!("Running wlansoftmac with AP role");
let sta_addr = match sta_addr {
Some(sta_addr) => sta_addr,
None => {
error!("Driver provided no STA address.");
return Err(zx::Status::INTERNAL);
}
};
let config = ieee80211::Bssid::from(sta_addr);
Box::pin(wlan_mlme::mlme_main_loop::<wlan_mlme::ap::Ap<D>>(
mlme_init_sender,
config,
device,
buffer_provider,
mlme_request_stream,
driver_event_stream,
))
}
unsupported => {
error!("Unsupported mac role: {:?}", unsupported);
return Err(zx::Status::INTERNAL);
}
};
Ok(StartedDriver { softmac_ifc_bridge_request_stream, mlme, sme })
}
/// Await on futures hosting the following three servers:
///
/// - WlanSoftmacIfcBridge server
/// - MLME server
/// - SME server
///
/// The WlanSoftmacIfcBridge server runs on a parallel thread but will be shut down before this
/// function returns. This is true even if this function exits with an error.
///
/// Upon receiving a DriverEvent::Stop, the MLME server will shut down first. Then this function
/// will await the completion of WlanSoftmacIfcBridge server and SME server. Both will shut down as
/// a consequence of MLME server shut down.
async fn serve<F>(
init_completer: InitCompleter<F>,
mlme_init_receiver: oneshot::Receiver<Result<(), zx::Status>>,
driver_event_sink: DriverEventSink,
softmac_ifc_bridge_request_stream: fidl_softmac::WlanSoftmacIfcBridgeRequestStream,
mlme: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>,
sme: Pin<Box<impl Future<Output = Result<(), Error>>>>,
) -> Result<(), zx::Status>
where
F: FnOnce(Result<(), zx::Status>) + Send,
{
wtrace::duration_begin_scope!(c"rust_driver::serve");
// Create a oneshot::channel to signal to this executor when WlanSoftmacIfcBridge
// server exits.
let (bridge_exit_sender, bridge_exit_receiver) = oneshot::channel();
// Spawn a Task to host the WlanSoftmacIfcBridge server.
let bridge = Task::spawn(async move {
let _: Result<(), ()> = bridge_exit_sender
.send(
serve_wlan_softmac_ifc_bridge(driver_event_sink, softmac_ifc_bridge_request_stream)
.await,
)
.map_err(|result| {
error!("Failed to send serve_wlan_softmac_ifc_bridge() result: {:?}", result)
});
});
let mut mlme = mlme.fuse();
let mut sme = sme.fuse();
// oneshot::Receiver implements FusedFuture incorrectly, so we must call .fuse()
// to get the right behavior in the select!().
//
// See https://github.com/rust-lang/futures-rs/issues/2455 for more details.
let mut bridge_exit_receiver = bridge_exit_receiver.fuse();
let mut mlme_init_receiver = mlme_init_receiver.fuse();
// Run the MLME server and wait for the MLME to signal initialization completion.
{
wtrace::duration_begin_scope!(c"initialize MLME");
futures::select! {
init_result = mlme_init_receiver => {
let init_result = match init_result {
Ok(x) => x,
Err(e) => {
error!("mlme_init_receiver interrupted: {}", e);
let status = zx::Status::INTERNAL;
std::mem::drop(bridge);
init_completer.complete(Err(status));
return Err(status);
}
};
match init_result {
Ok(()) =>
init_completer.complete(Ok(())),
Err(status) => {
error!("Failed to initialize MLME: {}", status);
std::mem::drop(bridge);
init_completer.complete(Err(status));
return Err(status);
}
}
},
mlme_result = mlme => {
error!("MLME future completed before signaling init_sender: {:?}", mlme_result);
let status = zx::Status::INTERNAL;
std::mem::drop(bridge);
init_completer.complete(Err(status));
return Err(status);
}
}
}
// Run the SME and MLME servers.
let server_shutdown_result = {
wtrace::duration_begin_scope!(c"run MLME and SME");
futures::select! {
mlme_result = mlme => {
match mlme_result {
Ok(()) => {
info!("MLME shut down gracefully.");
Ok(())
},
Err(e) => {
error!("MLME shut down with error: {}", e);
Err(zx::Status::INTERNAL)
}
}
}
bridge_result = bridge_exit_receiver => {
error!("SoftmacIfcBridge server shut down before MLME: {:?}", bridge_result);
Err(zx::Status::INTERNAL)
}
sme_result = sme => {
error!("SME shut down before MLME: {:?}", sme_result);
Err(zx::Status::INTERNAL)
}}
};
// If any future returns an error, return with the same error without waiting for other futures
// to complete.
server_shutdown_result?;
// At this point, the `bridge` Task should not report a result because the WlanSoftmacIfcBridge
// server is still running. This cancellation should cause `bridge_exit_receiver` to be dropped.
bridge.cancel().map(|()| warn!("SoftmacIfcBridge server task completed before cancelation."));
let bridge_result: Result<(), ()> = match bridge_exit_receiver.await {
Err(Canceled) => {
// This is the expected case because when MLME shuts down, the SoftmacIfcBridge server
// task should still be running.
info!("SoftmacIfcBridge server shut down gracefully");
Ok(())
}
Ok(result) => {
warn!(
"SoftmacIfcBridge server task completed without canceling bridge_exit_receiver.\n\
This indicates the server was already shut down before its task was canceled."
);
result.map_err(|e| error!("SoftmacIfcBridge server shut down with error: {}", e))
}
};
// Since the MLME server is shut down at this point, the SME server will shut down soon because
// the SME server always shuts down when it loses connection with the MLME server.
let sme_result: Result<(), ()> = sme
.await
.map(|()| info!("SME shut down gracefully"))
.map_err(|e| error!("SME shut down with error: {}", e));
bridge_result.and(sme_result).map_err(|()| zx::Status::INTERNAL)
}
struct BootstrappedGenericSme {
pub generic_sme_request_stream: fidl_sme::GenericSmeRequestStream,
pub legacy_privacy_support: fidl_sme::LegacyPrivacySupport,
pub inspect_node: InspectNode,
}
/// Call WlanSoftmac.Start() to retrieve the server end of UsmeBootstrap channel and wait
/// for a UsmeBootstrap.Start() message to provide the server end of a GenericSme channel.
///
/// Any errors encountered in this function are fatal for the wlansoftmac driver. Failure to
/// bootstrap GenericSme request stream will result in a driver no other component can communicate
/// with.
async fn bootstrap_generic_sme<D: DeviceOps>(
device: &mut D,
driver_event_sink: DriverEventSink,
softmac_ifc_bridge_proxy: fidl_softmac::WlanSoftmacIfcBridgeProxy,
) -> Result<BootstrappedGenericSme, zx::Status> {
wtrace::duration!(c"rust_driver::bootstrap_generic_sme");
let wlan_softmac_ifc_bridge_client_handle = zx::Handle::from(
softmac_ifc_bridge_proxy
.into_channel()
.map_err(|_| {
error!(
"Failed to convert {} into channel.",
fidl_softmac::WlanSoftmacIfcBridgeMarker::DEBUG_NAME
);
zx::Status::INTERNAL
})?
.into_zx_channel(),
)
.into_raw();
// Calling WlanSoftmac.Start() indicates to the vendor driver that this driver (wlansoftmac) is
// ready to receive WlanSoftmacIfc messages. wlansoftmac will buffer all WlanSoftmacIfc messages
// in an mpsc::UnboundedReceiver<DriverEvent> sink until the MLME server drains them.
let usme_bootstrap_handle_via_iface_creation =
match device.start(driver_event_sink, wlan_softmac_ifc_bridge_client_handle) {
Ok(handle) => handle,
Err(status) => {
error!("Failed to receive a UsmeBootstrap handle: {}", status);
return Err(status);
}
};
let channel = zx::Channel::from(usme_bootstrap_handle_via_iface_creation);
let server = fidl::endpoints::ServerEnd::<fidl_sme::UsmeBootstrapMarker>::new(channel);
let mut usme_bootstrap_stream = match server.into_stream() {
Ok(res) => res,
Err(e) => {
error!("Failed to create a UsmeBootstrap request stream: {}", e);
return Err(zx::Status::INTERNAL);
}
};
let (generic_sme_server, legacy_privacy_support, responder) =
match usme_bootstrap_stream.next().await {
Some(Ok(fidl_sme::UsmeBootstrapRequest::Start {
generic_sme_server,
legacy_privacy_support,
responder,
..
})) => (generic_sme_server, legacy_privacy_support, responder),
Some(Err(e)) => {
error!("Received an error on USME bootstrap request stream: {}", e);
return Err(zx::Status::INTERNAL);
}
None => {
// This is always an error because the SME server should not drop
// the USME client endpoint until MLME shut down first.
error!("USME bootstrap stream terminated unexpectedly");
return Err(zx::Status::INTERNAL);
}
};
let inspector =
Inspector::new(fuchsia_inspect::InspectorConfig::default().size(INSPECT_VMO_SIZE_BYTES));
let inspect_node = inspector.root().create_child("usme");
let inspect_vmo = match inspector.duplicate_vmo() {
Some(vmo) => vmo,
None => {
error!("Failed to duplicate inspect VMO");
return Err(zx::Status::INTERNAL);
}
};
if let Err(e) = responder.send(inspect_vmo).into() {
error!("Failed to respond to UsmeBootstrap.Start(): {}", e);
return Err(zx::Status::INTERNAL);
}
let generic_sme_request_stream = match generic_sme_server.into_stream() {
Ok(stream) => stream,
Err(e) => {
error!("Failed to create GenericSme request stream: {}", e);
return Err(zx::Status::INTERNAL);
}
};
Ok(BootstrappedGenericSme { generic_sme_request_stream, legacy_privacy_support, inspect_node })
}
async fn serve_wlan_softmac_ifc_bridge(
driver_event_sink: DriverEventSink,
mut softmac_ifc_bridge_request_stream: fidl_softmac::WlanSoftmacIfcBridgeRequestStream,
) -> Result<(), anyhow::Error> {
loop {
let request = match softmac_ifc_bridge_request_stream.next().await {
Some(Ok(request)) => request,
Some(Err(e)) => {
return Err(format_err!("WlanSoftmacIfcBridge server stream failed: {}", e));
}
None => {
info!("WlanSoftmacIfcBridge stream terminated");
return Ok(());
}
};
match request {
fidl_softmac::WlanSoftmacIfcBridgeRequest::ReportTxResult { tx_result, responder } => {
let responder = driver_event_sink.unbounded_send_or_respond(
DriverEvent::TxResultReport { tx_result },
responder,
(),
)?;
responder.send().format_send_err_with_context("ReportTxResult")?;
}
fidl_softmac::WlanSoftmacIfcBridgeRequest::NotifyScanComplete {
payload,
responder,
} => {
let ((status, scan_id), responder) = responder.unpack_fields_or_respond((
payload.status.with_name("status"),
payload.scan_id.with_name("scan_id"),
))?;
let status = zx::Status::from_raw(status);
let responder = driver_event_sink.unbounded_send_or_respond(
DriverEvent::ScanComplete { status, scan_id },
responder,
(),
)?;
responder.send().format_send_err_with_context("NotifyScanComplete")?;
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
anyhow::format_err,
diagnostics_assertions::assert_data_tree,
fuchsia_async::TestExecutor,
fuchsia_inspect::InspectorConfig,
futures::{stream::FuturesUnordered, task::Poll},
std::pin::pin,
test_case::test_case,
wlan_common::assert_variant,
wlan_mlme::device::test_utils::{FakeDevice, FakeDeviceConfig},
zx::Vmo,
};
struct BootstrapGenericSmeTestHarness {
_softmac_ifc_bridge_request_stream: fidl_softmac::WlanSoftmacIfcBridgeRequestStream,
}
// We could implement BootstrapGenericSmeTestHarness::new() instead of a macro, but doing so requires
// pinning the FakeDevice and WlanSoftmacIfcProtocol (and its associated DriverEventSink). While the
// pinning itself is feasible, it leads to a complex harness implementation that outweighs the benefit
// of using a harness to begin with.
macro_rules! make_bootstrap_generic_sme_test_harness {
(&mut $fake_device:ident, $driver_event_sink:ident $(,)?) => {{
let (softmac_ifc_bridge_proxy, _softmac_ifc_bridge_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_softmac::WlanSoftmacIfcBridgeMarker>().unwrap();
(
Box::pin(bootstrap_generic_sme(
&mut $fake_device,
$driver_event_sink,
softmac_ifc_bridge_proxy,
)),
BootstrapGenericSmeTestHarness {
_softmac_ifc_bridge_request_stream,
}
)
}};
}
#[fuchsia::test(allow_stalls = false)]
async fn bootstrap_generic_sme_fails_to_retrieve_usme_bootstrap_handle() {
let (mut fake_device, _fake_device_state) = FakeDevice::new_with_config(
FakeDeviceConfig::default().with_mock_start_result(Err(zx::Status::INTERRUPTED_RETRY)),
)
.await;
let (driver_event_sink, _driver_event_stream) = DriverEventSink::new();
let (mut bootstrap_generic_sme_fut, _harness) =
make_bootstrap_generic_sme_test_harness!(&mut fake_device, driver_event_sink);
match TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await {
Poll::Ready(Err(zx::Status::INTERRUPTED_RETRY)) => (),
Poll::Ready(Err(status)) => panic!("Failed with wrong status: {}", status),
Poll::Ready(Ok(_)) => panic!("Succeeded unexpectedly"),
Poll::Pending => panic!("bootstrap_generic_sme() unexpectedly stalled"),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn boostrap_generic_sme_fails_on_error_from_bootstrap_stream() {
let (mut fake_device, fake_device_state) =
FakeDevice::new_with_config(FakeDeviceConfig::default()).await;
let (driver_event_sink, _driver_event_stream) = DriverEventSink::new();
let (mut bootstrap_generic_sme_fut, _harness) =
make_bootstrap_generic_sme_test_harness!(&mut fake_device, driver_event_sink);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await,
Poll::Pending
));
// Write an invalid FIDL message to the USME bootstrap channel.
let usme_bootstrap_channel =
fake_device_state.lock().usme_bootstrap_client_end.take().unwrap().into_channel();
usme_bootstrap_channel.write(&[], &mut []).unwrap();
assert!(matches!(
TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
));
}
#[fuchsia::test(allow_stalls = false)]
async fn boostrap_generic_sme_fails_on_closed_bootstrap_stream() {
let (mut fake_device, fake_device_state) =
FakeDevice::new_with_config(FakeDeviceConfig::default()).await;
let (driver_event_sink, _driver_event_stream) = DriverEventSink::new();
let (mut bootstrap_generic_sme_fut, _harness) =
make_bootstrap_generic_sme_test_harness!(&mut fake_device, driver_event_sink);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await,
Poll::Pending
));
// Drop the client end of USME bootstrap channel.
let _ = fake_device_state.lock().usme_bootstrap_client_end.take().unwrap();
assert!(matches!(
TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
));
}
#[fuchsia::test(allow_stalls = false)]
async fn boostrap_generic_sme_succeeds() {
let (mut fake_device, fake_device_state) =
FakeDevice::new_with_config(FakeDeviceConfig::default()).await;
let (driver_event_sink, _driver_event_stream) = DriverEventSink::new();
let (mut bootstrap_generic_sme_fut, _harness) =
make_bootstrap_generic_sme_test_harness!(&mut fake_device, driver_event_sink);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await,
Poll::Pending
));
let usme_bootstrap_proxy = fake_device_state
.lock()
.usme_bootstrap_client_end
.take()
.unwrap()
.into_proxy()
.unwrap();
let sent_legacy_privacy_support =
fidl_sme::LegacyPrivacySupport { wep_supported: false, wpa1_supported: false };
let (generic_sme_proxy, generic_sme_server) =
fidl::endpoints::create_proxy::<fidl_sme::GenericSmeMarker>().unwrap();
let inspect_vmo_fut =
usme_bootstrap_proxy.start(generic_sme_server, &sent_legacy_privacy_support);
let mut inspect_vmo_fut = pin!(inspect_vmo_fut);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut inspect_vmo_fut).await,
Poll::Pending
));
let BootstrappedGenericSme {
mut generic_sme_request_stream,
legacy_privacy_support: received_legacy_privacy_support,
inspect_node,
} = match TestExecutor::poll_until_stalled(&mut bootstrap_generic_sme_fut).await {
Poll::Pending => panic!("bootstrap_generic_sme_fut() did not complete!"),
Poll::Ready(x) => x.unwrap(),
};
let inspect_vmo = match TestExecutor::poll_until_stalled(&mut inspect_vmo_fut).await {
Poll::Pending => panic!("Failed to receive an inspect VMO."),
Poll::Ready(x) => x.unwrap(),
};
// Send a GenericSme.Query() to check the generic_sme_proxy
// and generic_sme_stream are connected.
let query_fut = generic_sme_proxy.query();
let mut query_fut = pin!(query_fut);
assert!(matches!(TestExecutor::poll_until_stalled(&mut query_fut).await, Poll::Pending));
let next_generic_sme_request_fut = generic_sme_request_stream.next();
let mut next_generic_sme_request_fut = pin!(next_generic_sme_request_fut);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut next_generic_sme_request_fut).await,
Poll::Ready(Some(Ok(fidl_sme::GenericSmeRequest::Query { .. })))
));
assert_eq!(received_legacy_privacy_support, sent_legacy_privacy_support);
// Add a child node through inspect_node and verify the node appears inspect_vmo.
let inspector = Inspector::new(InspectorConfig::default().vmo(inspect_vmo));
let _a = inspect_node.create_child("a");
assert_data_tree!(inspector, root: {
usme: { a: {} },
});
}
struct StartTestHarness {
#[allow(dead_code)]
pub mlme_init_receiver: Pin<Box<oneshot::Receiver<Result<(), zx::Status>>>>,
#[allow(dead_code)]
pub driver_event_sink: DriverEventSink,
}
impl StartTestHarness {
fn new(
fake_device: FakeDevice,
) -> (
impl Future<
Output = Result<
StartedDriver<
Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>,
Pin<Box<impl Future<Output = Result<(), Error>>>>,
>,
zx::Status,
>,
>,
Self,
) {
let (mlme_init_sender, mlme_init_receiver) = oneshot::channel();
let (driver_event_sink, driver_event_stream) = DriverEventSink::new();
let fake_buffer_provider = wlan_mlme::buffer::FakeCBufferProvider::new();
(
Box::pin(start(
mlme_init_sender,
driver_event_sink.clone(),
driver_event_stream,
fake_device,
fake_buffer_provider,
)),
Self { mlme_init_receiver: Box::pin(mlme_init_receiver), driver_event_sink },
)
}
}
#[fuchsia::test(allow_stalls = false)]
async fn start_fails_on_bad_bootstrap() {
let (fake_device, _fake_device_state) = FakeDevice::new_with_config(
FakeDeviceConfig::default().with_mock_start_result(Err(zx::Status::INTERRUPTED_RETRY)),
)
.await;
let (mut start_fut, _harness) = StartTestHarness::new(fake_device);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut start_fut).await,
Poll::Ready(Err(zx::Status::INTERRUPTED_RETRY))
));
}
fn bootstrap_generic_sme_proxy_and_inspect_vmo(
usme_bootstrap_client_end: fidl::endpoints::ClientEnd<fidl_sme::UsmeBootstrapMarker>,
) -> (fidl_sme::GenericSmeProxy, impl Future<Output = Result<Vmo, fidl::Error>>) {
let usme_client_proxy = usme_bootstrap_client_end
.into_proxy()
.expect("Failed to set up the USME client proxy.");
let legacy_privacy_support =
fidl_sme::LegacyPrivacySupport { wep_supported: false, wpa1_supported: false };
let (generic_sme_proxy, generic_sme_server) =
fidl::endpoints::create_proxy::<fidl_sme::GenericSmeMarker>().unwrap();
(generic_sme_proxy, usme_client_proxy.start(generic_sme_server, &legacy_privacy_support))
}
#[test_case(FakeDeviceConfig::default().with_mock_query_response(Err(zx::Status::IO_DATA_INTEGRITY)), zx::Status::IO_DATA_INTEGRITY)]
#[test_case(FakeDeviceConfig::default().with_mock_mac_sublayer_support(Err(zx::Status::IO_DATA_INTEGRITY)), zx::Status::IO_DATA_INTEGRITY)]
#[test_case(FakeDeviceConfig::default().with_mock_mac_implementation_type(fidl_common::MacImplementationType::Fullmac), zx::Status::INTERNAL)]
#[test_case(FakeDeviceConfig::default().with_mock_security_support(Err(zx::Status::IO_DATA_INTEGRITY)), zx::Status::IO_DATA_INTEGRITY)]
#[test_case(FakeDeviceConfig::default().with_mock_spectrum_management_support(Err(zx::Status::IO_DATA_INTEGRITY)), zx::Status::IO_DATA_INTEGRITY)]
#[test_case(FakeDeviceConfig::default().with_mock_mac_role(fidl_common::WlanMacRole::__SourceBreaking { unknown_ordinal: 0 }), zx::Status::INTERNAL)]
#[fuchsia::test(allow_stalls = false)]
async fn start_fails_on_query_error(
fake_device_config: FakeDeviceConfig,
expected_status: zx::Status,
) {
let (fake_device, fake_device_state) =
FakeDevice::new_with_config(fake_device_config).await;
let (mut start_fut, _harness) = StartTestHarness::new(fake_device);
let usme_bootstrap_client_end =
fake_device_state.lock().usme_bootstrap_client_end.take().unwrap();
let (_generic_sme_proxy, _inspect_vmo_fut) =
bootstrap_generic_sme_proxy_and_inspect_vmo(usme_bootstrap_client_end);
match TestExecutor::poll_until_stalled(&mut start_fut).await {
Poll::Ready(Err(status)) => assert_eq!(status, expected_status),
Poll::Pending => panic!("start_fut still pending!"),
Poll::Ready(Ok(_)) => panic!("start_fut completed with Ok value"),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn start_fail_on_dropped_mlme_event_stream() {
let (fake_device, fake_device_state) = FakeDevice::new().await;
let (mut start_fut, _harness) = StartTestHarness::new(fake_device);
let usme_bootstrap_client_end =
fake_device_state.lock().usme_bootstrap_client_end.take().unwrap();
let (_generic_sme_proxy, _inspect_vmo_fut) =
bootstrap_generic_sme_proxy_and_inspect_vmo(usme_bootstrap_client_end);
let _ = fake_device_state.lock().mlme_event_stream.take();
match TestExecutor::poll_until_stalled(&mut start_fut).await {
Poll::Ready(Err(status)) => assert_eq!(status, zx::Status::INTERNAL),
Poll::Pending => panic!("start_fut still pending!"),
Poll::Ready(Ok(_)) => panic!("start_fut completed with Ok value"),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn start_succeeds() {
let (fake_device, fake_device_state) = FakeDevice::new_with_config(
FakeDeviceConfig::default()
.with_mock_sta_addr([2u8; 6])
.with_mock_mac_role(fidl_common::WlanMacRole::Client),
)
.await;
let (mut start_fut, mut harness) = StartTestHarness::new(fake_device);
let usme_bootstrap_client_end =
fake_device_state.lock().usme_bootstrap_client_end.take().unwrap();
let (generic_sme_proxy, _inspect_vmo_fut) =
bootstrap_generic_sme_proxy_and_inspect_vmo(usme_bootstrap_client_end);
let StartedDriver {
softmac_ifc_bridge_request_stream: _softmac_ifc_bridge_request_stream,
mut mlme,
sme,
} = match TestExecutor::poll_until_stalled(&mut start_fut).await {
Poll::Ready(Ok(x)) => x,
Poll::Ready(Err(status)) => {
panic!("start_fut unexpectedly failed; {}", status)
}
Poll::Pending => panic!("start_fut still pending!"),
};
assert_variant!(TestExecutor::poll_until_stalled(&mut mlme).await, Poll::Pending);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut harness.mlme_init_receiver).await,
Poll::Ready(Ok(Ok(())))
));
let resp_fut = generic_sme_proxy.query();
let mut resp_fut = pin!(resp_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
let sme_and_mlme = [sme, mlme].into_iter().collect::<FuturesUnordered<_>>();
let mut sme_and_mlme = pin!(sme_and_mlme);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut sme_and_mlme.next()).await,
Poll::Pending
));
assert!(matches!(
TestExecutor::poll_until_stalled(&mut resp_fut).await,
Poll::Ready(Ok(fidl_sme::GenericSmeQuery {
role: fidl_common::WlanMacRole::Client,
sta_addr: [2, 2, 2, 2, 2, 2],
}))
));
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_wlansoftmac_ifc_bridge_fails_on_request_stream_error() {
let (driver_event_sink, _driver_event_stream) = DriverEventSink::new();
let (softmac_ifc_bridge_client, softmac_ifc_bridge_server) =
fidl::endpoints::create_endpoints::<fidl_softmac::WlanSoftmacIfcBridgeMarker>();
let softmac_ifc_bridge_request_stream = softmac_ifc_bridge_server.into_stream().unwrap();
let softmac_ifc_bridge_channel = softmac_ifc_bridge_client.into_channel();
let server_fut =
serve_wlan_softmac_ifc_bridge(driver_event_sink, softmac_ifc_bridge_request_stream);
let mut server_fut = pin!(server_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut server_fut).await, Poll::Pending);
softmac_ifc_bridge_channel.write(&[], &mut []).unwrap();
assert_variant!(
TestExecutor::poll_until_stalled(&mut server_fut).await,
Poll::Ready(Err(_))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_wlansoftmac_ifc_bridge_exits_on_request_stream_end() {
let (driver_event_sink, _driver_event_stream) = DriverEventSink::new();
let (softmac_ifc_bridge_client, softmac_ifc_bridge_server) =
fidl::endpoints::create_endpoints::<fidl_softmac::WlanSoftmacIfcBridgeMarker>();
let softmac_ifc_bridge_request_stream = softmac_ifc_bridge_server.into_stream().unwrap();
let server_fut =
serve_wlan_softmac_ifc_bridge(driver_event_sink, softmac_ifc_bridge_request_stream);
let mut server_fut = pin!(server_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut server_fut).await, Poll::Pending);
drop(softmac_ifc_bridge_client);
assert_variant!(
TestExecutor::poll_until_stalled(&mut server_fut).await,
Poll::Ready(Ok(()))
);
}
#[test_case(fidl_softmac::WlanSoftmacIfcBaseNotifyScanCompleteRequest {
status: None,
scan_id: Some(754),
..Default::default()
})]
#[test_case(fidl_softmac::WlanSoftmacIfcBaseNotifyScanCompleteRequest {
status: Some(zx::Status::OK.into_raw()),
scan_id: None,
..Default::default()
})]
#[fuchsia::test(allow_stalls = false)]
async fn serve_wlansoftmac_ifc_bridge_exits_on_invalid_notify_scan_complete_request(
request: fidl_softmac::WlanSoftmacIfcBaseNotifyScanCompleteRequest,
) {
let (driver_event_sink, mut driver_event_stream) = DriverEventSink::new();
let (softmac_ifc_bridge_proxy, softmac_ifc_bridge_server) =
fidl::endpoints::create_proxy::<fidl_softmac::WlanSoftmacIfcBridgeMarker>().unwrap();
let softmac_ifc_bridge_request_stream = softmac_ifc_bridge_server.into_stream().unwrap();
let server_fut =
serve_wlan_softmac_ifc_bridge(driver_event_sink, softmac_ifc_bridge_request_stream);
let mut server_fut = pin!(server_fut);
let resp_fut = softmac_ifc_bridge_proxy.notify_scan_complete(&request);
let mut resp_fut = pin!(resp_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
assert_variant!(
TestExecutor::poll_until_stalled(&mut server_fut).await,
Poll::Ready(Err(_))
);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Ready(Ok(())));
assert!(matches!(driver_event_stream.try_next(), Ok(None)));
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_wlansoftmac_ifc_bridge_enqueues_notify_scan_complete() {
let (driver_event_sink, mut driver_event_stream) = DriverEventSink::new();
let (softmac_ifc_bridge_proxy, softmac_ifc_bridge_server) =
fidl::endpoints::create_proxy::<fidl_softmac::WlanSoftmacIfcBridgeMarker>().unwrap();
let softmac_ifc_bridge_request_stream = softmac_ifc_bridge_server.into_stream().unwrap();
let server_fut =
serve_wlan_softmac_ifc_bridge(driver_event_sink, softmac_ifc_bridge_request_stream);
let mut server_fut = pin!(server_fut);
let resp_fut = softmac_ifc_bridge_proxy.notify_scan_complete(
&fidl_softmac::WlanSoftmacIfcBaseNotifyScanCompleteRequest {
status: Some(zx::Status::OK.into_raw()),
scan_id: Some(754),
..Default::default()
},
);
let mut resp_fut = pin!(resp_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
assert_variant!(TestExecutor::poll_until_stalled(&mut server_fut).await, Poll::Pending);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Ready(Ok(())));
assert!(matches!(
driver_event_stream.try_next(),
Ok(Some(DriverEvent::ScanComplete { status: zx::Status::OK, scan_id: 754 }))
));
}
struct ServeTestHarness {
pub init_complete_receiver: Pin<Box<oneshot::Receiver<Result<(), zx::Status>>>>,
pub mlme_init_sender: oneshot::Sender<Result<(), zx::Status>>,
#[allow(dead_code)]
pub driver_event_stream: mpsc::UnboundedReceiver<DriverEvent>,
#[allow(dead_code)]
pub softmac_ifc_bridge_proxy: fidl_softmac::WlanSoftmacIfcBridgeProxy,
pub complete_mlme_sender: oneshot::Sender<Result<(), anyhow::Error>>,
pub complete_sme_sender: oneshot::Sender<Result<(), anyhow::Error>>,
}
impl ServeTestHarness {
fn new() -> (Pin<Box<impl Future<Output = Result<(), zx::Status>>>>, ServeTestHarness) {
let (init_complete_sender, init_complete_receiver) = oneshot::channel();
let init_completer = InitCompleter::new(move |result| {
init_complete_sender.send(result).unwrap();
});
let (mlme_init_sender, mlme_init_receiver) = oneshot::channel();
let (driver_event_sink, driver_event_stream) = DriverEventSink::new();
let (softmac_ifc_bridge_proxy, softmac_ifc_bridge_server) =
fidl::endpoints::create_proxy::<fidl_softmac::WlanSoftmacIfcBridgeMarker>()
.unwrap();
let softmac_ifc_bridge_request_stream =
softmac_ifc_bridge_server.into_stream().unwrap();
let (complete_mlme_sender, complete_mlme_receiver) = oneshot::channel();
let mlme = Box::pin(async { complete_mlme_receiver.await.unwrap() });
let (complete_sme_sender, complete_sme_receiver) = oneshot::channel();
let sme = Box::pin(async { complete_sme_receiver.await.unwrap() });
(
Box::pin(serve(
init_completer,
mlme_init_receiver,
driver_event_sink,
softmac_ifc_bridge_request_stream,
mlme,
sme,
)),
ServeTestHarness {
init_complete_receiver: Box::pin(init_complete_receiver),
mlme_init_sender,
driver_event_stream,
softmac_ifc_bridge_proxy,
complete_mlme_sender,
complete_sme_sender,
},
)
}
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_wlansoftmac_ifc_bridge_enqueues_report_tx_result() {
let (driver_event_sink, mut driver_event_stream) = DriverEventSink::new();
let (softmac_ifc_bridge_proxy, softmac_ifc_bridge_server) =
fidl::endpoints::create_proxy::<fidl_softmac::WlanSoftmacIfcBridgeMarker>().unwrap();
let softmac_ifc_bridge_request_stream = softmac_ifc_bridge_server.into_stream().unwrap();
let server_fut =
serve_wlan_softmac_ifc_bridge(driver_event_sink, softmac_ifc_bridge_request_stream);
let mut server_fut = pin!(server_fut);
let resp_fut = softmac_ifc_bridge_proxy.report_tx_result(&fidl_common::WlanTxResult {
tx_result_entry: [fidl_common::WlanTxResultEntry {
tx_vector_idx: fidl_common::WLAN_TX_VECTOR_IDX_INVALID,
attempts: 0,
}; fidl_common::WLAN_TX_RESULT_MAX_ENTRY as usize],
peer_addr: [3; 6],
result_code: fidl_common::WlanTxResultCode::Failed,
});
let mut resp_fut = pin!(resp_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
assert_variant!(TestExecutor::poll_until_stalled(&mut server_fut).await, Poll::Pending);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Ready(Ok(())));
match driver_event_stream.try_next().unwrap().unwrap() {
DriverEvent::TxResultReport { tx_result } => {
assert_eq!(
tx_result,
fidl_common::WlanTxResult {
tx_result_entry: [fidl_common::WlanTxResultEntry {
tx_vector_idx: fidl_common::WLAN_TX_VECTOR_IDX_INVALID,
attempts: 0
};
fidl_common::WLAN_TX_RESULT_MAX_ENTRY as usize],
peer_addr: [3; 6],
result_code: fidl_common::WlanTxResultCode::Failed,
}
);
}
_ => panic!("Unexpected DriverEvent!"),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_exits_with_error_if_mlme_init_sender_dropped() {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
drop(harness.mlme_init_sender);
assert_variant!(
TestExecutor::poll_until_stalled(&mut serve_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Err(zx::Status::INTERNAL)))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_exits_with_error_on_init_failure() {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.mlme_init_sender.send(Err(zx::Status::IO_NOT_PRESENT)).unwrap();
assert_variant!(
TestExecutor::poll_until_stalled(&mut serve_fut).await,
Poll::Ready(Err(zx::Status::IO_NOT_PRESENT))
);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Err(zx::Status::IO_NOT_PRESENT)))
);
}
#[test_case(Ok(()))]
#[test_case(Err(format_err!("")))]
#[fuchsia::test(allow_stalls = false)]
async fn serve_exits_with_error_if_mlme_completes_before_init(
early_mlme_result: Result<(), Error>,
) {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.complete_mlme_sender.send(early_mlme_result).unwrap();
assert_variant!(
TestExecutor::poll_until_stalled(&mut serve_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Err(zx::Status::INTERNAL)))
);
}
#[test_case(Ok(()))]
#[test_case(Err(format_err!("")))]
#[fuchsia::test(allow_stalls = false)]
async fn serve_exits_with_error_if_sme_shuts_down_before_mlme(
early_sme_result: Result<(), Error>,
) {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.mlme_init_sender.send(Ok(())).unwrap();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Ok(())))
);
harness.complete_sme_sender.send(early_sme_result).unwrap();
assert_variant!(
TestExecutor::poll_until_stalled(&mut serve_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_exits_with_error_if_mlme_completes_with_error() {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.mlme_init_sender.send(Ok(())).unwrap();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Ok(())))
);
harness.complete_mlme_sender.send(Err(format_err!("mlme error"))).unwrap();
assert_eq!(
TestExecutor::poll_until_stalled(&mut serve_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_exits_with_error_if_sme_shuts_down_with_error() {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.mlme_init_sender.send(Ok(())).unwrap();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Ok(())))
);
harness.complete_mlme_sender.send(Ok(())).unwrap();
harness.complete_sme_sender.send(Err(format_err!("sme error"))).unwrap();
assert_eq!(
TestExecutor::poll_until_stalled(&mut serve_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn serve_shuts_down_gracefully() {
let (mut serve_fut, mut harness) = ServeTestHarness::new();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.mlme_init_sender.send(Ok(())).unwrap();
assert_variant!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
assert_variant!(
TestExecutor::poll_until_stalled(&mut harness.init_complete_receiver).await,
Poll::Ready(Ok(Ok(())))
);
harness.complete_mlme_sender.send(Ok(())).unwrap();
assert_eq!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Pending);
harness.complete_sme_sender.send(Ok(())).unwrap();
assert_eq!(TestExecutor::poll_until_stalled(&mut serve_fut).await, Poll::Ready(Ok(())));
}
#[derive(Debug)]
struct StartAndServeTestHarness<F> {
pub start_and_serve_fut: F,
pub softmac_handle_receiver: oneshot::Receiver<Result<WlanSoftmacHandle, zx::Status>>,
pub generic_sme_proxy: fidl_sme::GenericSmeProxy,
}
/// This function wraps start_and_serve() with a FakeDevice provided by a test.
///
/// A returned Ok value will contain a tuple with the following values if start_and_serve()
/// successfully bootstraps SME:
///
/// - start_and_serve() future.
/// - oneshot::Receiver to receive a WlanSoftmacHandle or an error.
/// - GenericSmeProxy
///
/// The returned start_and_serve() future will run the WlanSoftmacIfcBridge, MLME, and SME servers when
/// run on an executor.
///
/// An Err value will be returned if start_and_serve() encounters an error completing the bootstrap
/// of the SME server.
async fn start_and_serve_with_device(
fake_device: FakeDevice,
) -> Result<StartAndServeTestHarness<impl Future<Output = Result<(), zx::Status>>>, zx::Status>
{
let fake_buffer_provider = wlan_mlme::buffer::FakeCBufferProvider::new();
let (softmac_handle_sender, mut softmac_handle_receiver) =
oneshot::channel::<Result<WlanSoftmacHandle, zx::Status>>();
let start_and_serve_fut = start_and_serve(
move |result: Result<WlanSoftmacHandle, zx::Status>| {
softmac_handle_sender
.send(result)
.expect("Failed to signal initialization complete.")
},
fake_device.clone(),
fake_buffer_provider,
);
let mut start_and_serve_fut = Box::pin(start_and_serve_fut);
let usme_bootstrap_client_end = fake_device.state().lock().usme_bootstrap_client_end.take();
match usme_bootstrap_client_end {
// Simulate an errant initialization case where the UsmeBootstrap client end has been dropped
// during initialization.
None => match TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await {
Poll::Pending => panic!(
"start_and_serve() failed to panic when the UsmeBootstrap client was dropped."
),
Poll::Ready(result) => {
// Assert the same initialization error appears in the receiver too.
let status = result.unwrap_err();
assert_eq!(
status,
assert_variant!(
TestExecutor::poll_until_stalled(&mut softmac_handle_receiver).await,
Poll::Ready(Ok(Err(status))) => status
)
);
return Err(status);
}
},
// Simulate the normal initialization case where the the UsmeBootstrap client end is active
// during initialization.
Some(usme_bootstrap_client_end) => {
let (generic_sme_proxy, inspect_vmo_fut) =
bootstrap_generic_sme_proxy_and_inspect_vmo(usme_bootstrap_client_end);
let start_and_serve_fut = match TestExecutor::poll_until_stalled(
&mut start_and_serve_fut,
)
.await
{
Poll::Pending => start_and_serve_fut,
Poll::Ready(result) => {
// Assert the same initialization error appears in the receiver too.
let status = result.unwrap_err();
assert_eq!(
status,
assert_variant!(
TestExecutor::poll_until_stalled(&mut softmac_handle_receiver).await,
Poll::Ready(Ok(Err(status))) => status
)
);
return Err(status);
}
};
inspect_vmo_fut.await.expect("Failed to bootstrap USME.");
Ok(StartAndServeTestHarness {
start_and_serve_fut,
softmac_handle_receiver,
generic_sme_proxy,
})
}
}
}
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_fails_on_dropped_usme_bootstrap_client() {
let (fake_device, fake_device_state) = FakeDevice::new().await;
fake_device_state.lock().usme_bootstrap_client_end = None;
match start_and_serve_with_device(fake_device.clone()).await {
Ok(_) => panic!(
"start_and_serve() does not fail when the UsmeBootstrap client end is dropped."
),
Err(status) => assert_eq!(status, zx::Status::INTERNAL),
}
}
// Exhaustive feature tests are unit tested on start()
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_fails_with_wrong_mac_implementation_type() {
let (fake_device, _fake_device_state) = FakeDevice::new_with_config(
FakeDeviceConfig::default()
.with_mock_mac_implementation_type(fidl_common::MacImplementationType::Fullmac),
)
.await;
match start_and_serve_with_device(fake_device).await {
Ok(_) => panic!(
"start_and_serve() future did not terminate before attempting bootstrap SME."
),
Err(status) => assert_eq!(status, zx::Status::INTERNAL),
};
}
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_fails_on_dropped_mlme_event_stream() {
let (mut fake_device, _fake_device_state) = FakeDevice::new().await;
let _ = fake_device.take_mlme_event_stream();
match start_and_serve_with_device(fake_device.clone()).await {
Ok(_) => {
panic!("start_and_serve() does not fail when the MLME event stream is missing.")
}
Err(status) => assert_eq!(status, zx::Status::INTERNAL),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_fails_on_dropped_generic_sme_client() {
let (fake_device, _fake_device_state) = FakeDevice::new().await;
let StartAndServeTestHarness {
mut start_and_serve_fut,
mut softmac_handle_receiver,
generic_sme_proxy,
} = start_and_serve_with_device(fake_device)
.await
.expect("Failed to initiate wlansoftmac setup.");
let _handle = assert_variant!(TestExecutor::poll_until_stalled(&mut softmac_handle_receiver).await, Poll::Ready(Ok(Ok(handle))) => handle);
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
drop(generic_sme_proxy);
assert_eq!(
TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await,
Poll::Ready(Err(zx::Status::INTERNAL))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_shuts_down_gracefully() {
let (fake_device, _fake_device_state) = FakeDevice::new().await;
let StartAndServeTestHarness {
mut start_and_serve_fut,
mut softmac_handle_receiver,
generic_sme_proxy: _generic_sme_proxy,
} = start_and_serve_with_device(fake_device)
.await
.expect("Failed to initiate wlansoftmac setup.");
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
let handle = assert_variant!(TestExecutor::poll_until_stalled(&mut softmac_handle_receiver).await, Poll::Ready(Ok(Ok(handle))) => handle);
let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>();
handle.stop(StopCompleter::new(Box::new(move || {
shutdown_sender.send(()).expect("Failed to signal shutdown completion.")
})));
assert_variant!(futures::join!(start_and_serve_fut, shutdown_receiver), (Ok(()), Ok(())));
}
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_responds_to_generic_sme_requests() {
let (fake_device, _fake_device_state) = FakeDevice::new().await;
let StartAndServeTestHarness {
mut start_and_serve_fut,
mut softmac_handle_receiver,
generic_sme_proxy,
} = start_and_serve_with_device(fake_device)
.await
.expect("Failed to initiate wlansoftmac setup.");
let handle = assert_variant!(TestExecutor::poll_until_stalled(&mut softmac_handle_receiver).await, Poll::Ready(Ok(Ok(handle))) => handle);
let (sme_telemetry_proxy, sme_telemetry_server) =
fidl::endpoints::create_proxy().expect("Failed to create_proxy");
let (client_sme_proxy, client_sme_server) =
fidl::endpoints::create_proxy().expect("Failed to create_proxy");
let resp_fut = generic_sme_proxy.get_sme_telemetry(sme_telemetry_server);
let mut resp_fut = pin!(resp_fut);
// First poll `get_sme_telemetry` to send a `GetSmeTelemetry` request to the SME server, and then
// poll the SME server process it. Finally, expect `get_sme_telemetry` to complete with `Ok(())`.
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
assert_variant!(
TestExecutor::poll_until_stalled(&mut resp_fut).await,
Poll::Ready(Ok(Ok(())))
);
let resp_fut = generic_sme_proxy.get_client_sme(client_sme_server);
let mut resp_fut = pin!(resp_fut);
// First poll `get_client_sme` to send a `GetClientSme` request to the SME server, and then poll the
// SME server process it. Finally, expect `get_client_sme` to complete with `Ok(())`.
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
resp_fut.await.expect("Generic SME proxy failed").expect("Client SME request failed");
let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>();
handle.stop(StopCompleter::new(Box::new(move || {
shutdown_sender.send(()).expect("Failed to signal shutdown completion.")
})));
assert_variant!(futures::join!(start_and_serve_fut, shutdown_receiver), (Ok(()), Ok(())));
// All SME proxies should shutdown.
assert!(generic_sme_proxy.is_closed());
assert!(sme_telemetry_proxy.is_closed());
assert!(client_sme_proxy.is_closed());
}
// Mocking a passive scan verifies the path through SME, MLME, and the FFI is functional. Other paths
// are much more complex to mock and sufficiently covered by other testing. For example, queueing an
// Ethernet frame requires mocking an association first, and the outcome of a reported Tx result cannot
// be confirmed because the Minstrel is internal to MLME.
#[fuchsia::test(allow_stalls = false)]
async fn start_and_serve_responds_to_passive_scan_request() {
let (fake_device, fake_device_state) = FakeDevice::new().await;
let StartAndServeTestHarness {
mut start_and_serve_fut,
mut softmac_handle_receiver,
generic_sme_proxy,
} = start_and_serve_with_device(fake_device)
.await
.expect("Failed to initiate wlansoftmac setup.");
let handle = assert_variant!(TestExecutor::poll_until_stalled(&mut softmac_handle_receiver).await, Poll::Ready(Ok(Ok(handle))) => handle);
let (client_sme_proxy, client_sme_server) =
fidl::endpoints::create_proxy().expect("Failed to create_proxy");
let resp_fut = generic_sme_proxy.get_client_sme(client_sme_server);
let mut resp_fut = pin!(resp_fut);
assert_variant!(TestExecutor::poll_until_stalled(&mut resp_fut).await, Poll::Pending);
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut resp_fut).await,
Poll::Ready(Ok(Ok(())))
));
let scan_response_fut =
client_sme_proxy.scan(&fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
let mut scan_response_fut = pin!(scan_response_fut);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut scan_response_fut).await,
Poll::Pending
));
assert!(fake_device_state.lock().captured_passive_scan_request.is_none());
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
assert!(fake_device_state.lock().captured_passive_scan_request.is_some());
let wlan_softmac_ifc_bridge_proxy =
fake_device_state.lock().wlan_softmac_ifc_bridge_proxy.take().unwrap();
let notify_scan_complete_fut = wlan_softmac_ifc_bridge_proxy.notify_scan_complete(
&fidl_softmac::WlanSoftmacIfcBaseNotifyScanCompleteRequest {
status: Some(zx::Status::OK.into_raw()),
scan_id: Some(0),
..Default::default()
},
);
notify_scan_complete_fut.await.expect("Failed to receive NotifyScanComplete response");
assert_eq!(TestExecutor::poll_until_stalled(&mut start_and_serve_fut).await, Poll::Pending);
assert!(matches!(
TestExecutor::poll_until_stalled(&mut scan_response_fut).await,
Poll::Ready(Ok(_))
));
let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>();
handle.stop(StopCompleter::new(Box::new(move || {
shutdown_sender.send(()).expect("Failed to signal shutdown completion.")
})));
assert_variant!(futures::join!(start_and_serve_fut, shutdown_receiver), (Ok(()), Ok(())));
// All SME proxies should shutdown.
assert!(generic_sme_proxy.is_closed());
assert!(client_sme_proxy.is_closed());
}
}