blob: 9ec05417f69c6ab3b0cbae428d01da576f65c617 [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 anyhow::{format_err, Context as _, Error};
use fidl::endpoints;
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_device::MacRole;
use fidl_fuchsia_wlan_device_service::DeviceServiceProxy;
use fidl_fuchsia_wlan_sme as fidl_sme;
use fuchsia_syslog::{fx_log_err, fx_log_info};
use fuchsia_zircon as zx;
use futures::stream::TryStreamExt;
use fidl_fuchsia_wlan_device_service::{self as wlan_service};
type WlanService = DeviceServiceProxy;
const SCAN_TIMEOUT_SECONDS: u8 = 20;
// Helper methods for calling wlan_service fidl methods
pub async fn get_iface_list(wlan_svc: &DeviceServiceProxy) -> Result<Vec<u16>, Error> {
let response = wlan_svc.list_ifaces().await.context("Error getting iface list")?;
let mut wlan_iface_ids = Vec::new();
for iface in response.ifaces {
wlan_iface_ids.push(iface.iface_id);
}
Ok(wlan_iface_ids)
}
pub async fn get_iface_sme_proxy(
wlan_svc: &WlanService,
iface_id: u16,
) -> Result<fidl_sme::ClientSmeProxy, Error> {
let (sme_proxy, sme_remote) = endpoints::create_proxy()?;
let status = wlan_svc
.get_client_sme(iface_id, sme_remote)
.await
.context("error sending GetClientSme request")?;
if status == zx::sys::ZX_OK {
Ok(sme_proxy)
} else {
Err(format_err!("Invalid interface id {}", iface_id))
}
}
pub async fn get_first_client_sme(
wlan_svc: &WlanService,
) -> Result<fidl_sme::ClientSmeProxy, Error> {
let wlan_iface_ids =
get_iface_list(wlan_svc).await.context("Connect: failed to get wlan iface list")?;
if wlan_iface_ids.len() == 0 {
return Err(format_err!("No wlan interface found"));
}
fx_log_info!("Found {} wlan iface entries", wlan_iface_ids.len());
for iface_id in wlan_iface_ids {
let (status, resp) = wlan_svc.query_iface(iface_id).await.context("querying iface info")?;
if status != zx::sys::ZX_OK {
return Err(format_err!("query_iface {} failed: {}", iface_id, status));
}
if resp.is_none() {
return Err(format_err!("invalid response"));
}
let resp = resp.unwrap();
if resp.role == MacRole::Client {
return get_iface_sme_proxy(&wlan_svc, resp.id).await;
}
}
Err(format_err!("No client interface found"))
}
pub async fn connect_to_network(
iface_sme_proxy: &fidl_sme::ClientSmeProxy,
target_ssid: Vec<u8>,
target_pwd: Vec<u8>,
) -> Result<bool, Error> {
let (connection_proxy, connection_remote) = endpoints::create_proxy()?;
let target_ssid_clone = target_ssid.clone();
// create ConnectRequest holding network info
let credential = credential_from_password(target_pwd);
let mut req = fidl_sme::ConnectRequest {
ssid: target_ssid,
credential,
radio_cfg: fidl_sme::RadioConfig {
override_phy: false,
phy: fidl_common::Phy::Ht,
override_cbw: false,
cbw: fidl_common::Cbw::Cbw20,
override_primary_chan: false,
primary_chan: 0,
},
scan_type: fidl_common::ScanType::Passive,
};
let _result = iface_sme_proxy.connect(&mut req, Some(connection_remote))?;
let connection_code = handle_connect_transaction(connection_proxy).await?;
#[allow(unreachable_patterns)]
let mut connected = match connection_code {
fidl_sme::ConnectResultCode::Success => true,
fidl_sme::ConnectResultCode::Canceled => {
fx_log_err!("Connecting was canceled or superseded by another command");
false
}
fidl_sme::ConnectResultCode::Failed => {
fx_log_err!("Failed to connect to network");
false
}
fidl_sme::ConnectResultCode::BadCredentials => {
fx_log_err!("Failed to connect to network; bad credentials");
false
}
e => {
// also need to handle new result codes, generically return false here
fx_log_err!("Failed to connect: {:?}", e);
false
}
};
if connected == true {
let rsp =
iface_sme_proxy.status().await.context("failed to check status from sme_proxy")?;
connected = connected
&& match rsp.connected_to {
Some(ref bss) if bss.ssid.as_slice().to_vec() == target_ssid_clone => true,
Some(ref bss) => {
fx_log_err!(
"Connected to wrong network: {:?}. Expected: {:?}.",
bss.ssid.as_slice(),
target_ssid_clone
);
false
}
_ => false,
};
}
Ok(connected)
}
async fn handle_connect_transaction(
connect_transaction: fidl_sme::ConnectTransactionProxy,
) -> Result<fidl_sme::ConnectResultCode, Error> {
let mut event_stream = connect_transaction.take_event_stream();
let mut result_code = fidl_sme::ConnectResultCode::Failed;
while let Some(evt) = event_stream
.try_next()
.await
.context("failed to receive connect result before the channel was closed")?
{
match evt {
fidl_sme::ConnectTransactionEvent::OnFinished { code } => {
result_code = code;
break;
}
}
}
Ok(result_code)
}
pub async fn disconnect_from_network(
iface_sme_proxy: &fidl_sme::ClientSmeProxy,
) -> Result<(), Error> {
iface_sme_proxy.disconnect().await.context("failed to trigger disconnect")?;
// check the status and ensure we are not connected to or connecting to anything
let rsp = iface_sme_proxy.status().await.context("failed to check status from sme_proxy")?;
if rsp.connected_to.is_some() || !rsp.connecting_to_ssid.is_empty() {
return Err(format_err!(
"Disconnect confirmation failed: connected_to[{:?}] connecting_to_ssid:[{:?}]",
rsp.connected_to,
rsp.connecting_to_ssid
));
}
Ok(())
}
pub async fn disconnect_all_clients(wlan_svc: &WlanService) -> Result<(), Error> {
let wlan_iface_ids =
get_iface_list(wlan_svc).await.context("Connect: failed to get wlan iface list")?;
let mut error_msg = format!("");
for iface_id in wlan_iface_ids {
let (status, resp) = wlan_svc.query_iface(iface_id).await.context("querying iface info")?;
if status != zx::sys::ZX_OK {
error_msg = format!("{}failed querying iface {}: {}\n", error_msg, iface_id, status);
fx_log_err!("disconnect_all_clients: query err on iface {}: {}", iface_id, status);
continue;
}
if resp.is_none() {
error_msg = format!("{}no query response on iface {}\n", error_msg, iface_id);
fx_log_err!("disconnect_all_clients: iface query empty on iface {}", iface_id);
continue;
}
let resp = resp.unwrap();
if resp.role == MacRole::Client {
let sme_proxy = get_iface_sme_proxy(&wlan_svc, iface_id)
.await
.context("Disconnect all: failed to get iface sme proxy")?;
if let Err(e) = disconnect_from_network(&sme_proxy).await {
error_msg = format!("{}Error disconnecting iface {}: {}\n", error_msg, iface_id, e);
fx_log_err!("disconnect_all_clients: disconnect err on iface {}: {}", iface_id, e);
}
}
}
if error_msg.is_empty() {
Ok(())
} else {
Err(format_err!("{}", error_msg))
}
}
pub async fn perform_scan(
iface_sme_proxy: &fidl_sme::ClientSmeProxy,
) -> Result<Vec<fidl_sme::BssInfo>, Error> {
let scan_transaction = start_scan_transaction(&iface_sme_proxy)?;
get_scan_results(scan_transaction).await.map_err(Into::into)
}
fn start_scan_transaction(
iface_sme_proxy: &fidl_sme::ClientSmeProxy,
) -> Result<fidl_sme::ScanTransactionProxy, Error> {
let (scan_txn, remote) = endpoints::create_proxy()?;
let mut req = fidl_sme::ScanRequest {
timeout: SCAN_TIMEOUT_SECONDS,
scan_type: fidl_common::ScanType::Passive,
};
iface_sme_proxy.scan(&mut req, remote)?;
Ok(scan_txn)
}
async fn get_scan_results(
scan_txn: fidl_sme::ScanTransactionProxy,
) -> Result<Vec<fidl_sme::BssInfo>, Error> {
let mut stream = scan_txn.take_event_stream();
let mut scan_results = vec![];
while let Some(event) = stream
.try_next()
.await
.context("failed to receive scan result before the channel was closed")?
{
match event {
fidl_sme::ScanTransactionEvent::OnResult { aps } => scan_results.extend(aps),
fidl_sme::ScanTransactionEvent::OnFinished {} => return Ok(scan_results),
fidl_sme::ScanTransactionEvent::OnError { error } => {
// error while waiting for scan results
return Err(format_err!("error when retrieving scan results {:?}", error));
}
}
}
return Err(format_err!("ScanTransaction channel closed before scan finished"));
}
fn credential_from_password(pwd: Vec<u8>) -> fidl_sme::Credential {
if pwd.is_empty() {
fidl_sme::Credential::None(fidl_sme::Empty)
} else {
fidl_sme::Credential::Password(pwd)
}
}
pub async fn get_wlan_mac_addr(
wlan_svc: &DeviceServiceProxy,
iface_id: u16,
) -> Result<[u8; 6], Error> {
let (_status, resp) = wlan_svc.query_iface(iface_id).await?;
Ok(resp.ok_or(format_err!("No valid iface response"))?.mac_addr)
}
pub async fn destroy_iface(wlan_svc: &DeviceServiceProxy, iface_id: u16) -> Result<(), Error> {
let mut req = wlan_service::DestroyIfaceRequest { iface_id };
let response = wlan_svc.destroy_iface(&mut req).await.context("Error destroying iface")?;
match zx::Status::ok(response) {
Ok(()) => fx_log_info!("Destroyed iface {:?}", iface_id),
Err(s) => return Err(format_err!("Error destroying iface: {:?}", s)),
};
Ok(())
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::RequestStream,
fidl_fuchsia_wlan_device_service::{
self as wlan_service, DeviceServiceMarker, DeviceServiceProxy, DeviceServiceRequest,
DeviceServiceRequestStream, IfaceListItem, ListIfacesResponse, QueryIfaceResponse,
},
fidl_fuchsia_wlan_sme::{
BssInfo, ClientSmeMarker, ClientSmeRequest, ClientSmeRequestStream, ConnectResultCode,
Protection,
},
fuchsia_async::Executor,
futures::stream::{StreamExt, StreamFuture},
futures::task::Poll,
pin_utils::pin_mut,
wlan_common::assert_variant,
};
fn respond_to_query_iface_list_request(
exec: &mut Executor,
req_stream: &mut DeviceServiceRequestStream,
iface_list_vec: Vec<IfaceListItem>,
) {
let req = exec.run_until_stalled(&mut req_stream.next());
let responder = assert_variant !(
req,
Poll::Ready(Some(Ok(DeviceServiceRequest::ListIfaces{responder})))
=> responder);
responder
.send(&mut ListIfacesResponse { ifaces: iface_list_vec })
.expect("fake query list response: send failed")
}
fn extract_sme_server_from_get_client_sme_req_and_respond(
exec: &mut Executor,
req_stream: &mut DeviceServiceRequestStream,
status: zx::Status,
) -> fidl_sme::ClientSmeRequestStream {
let req = exec.run_until_stalled(&mut req_stream.next());
let (responder, fake_sme_server) = assert_variant !(
req,
Poll::Ready(Some(Ok(DeviceServiceRequest::GetClientSme{ iface_id:_, sme, responder})))
=> (responder, sme));
// now send the response back
responder.send(status.into_raw()).expect("fake sme proxy response: send failed");
// and return the stream
// let sme_stream = fake_sme_server.into_stream().expect("sme server stream failed");
// sme_stream
fake_sme_server.into_stream().expect("sme server stream failed")
}
fn respond_to_get_client_sme_request(
exec: &mut Executor,
req_stream: &mut DeviceServiceRequestStream,
status: zx::Status,
) {
let req = exec.run_until_stalled(&mut req_stream.next());
let responder = assert_variant !(
req,
Poll::Ready(Some(Ok(DeviceServiceRequest::GetClientSme{ responder, ..})))
=> responder);
// now send the response back
responder.send(status.into_raw()).expect("fake sme proxy response: send failed")
}
fn respond_to_client_sme_disconnect_request(
exec: &mut Executor,
req_stream: &mut ClientSmeRequestStream,
) {
let req = exec.run_until_stalled(&mut req_stream.next());
let responder = assert_variant !(
req,
Poll::Ready(Some(Ok(ClientSmeRequest::Disconnect{ responder})))
=> responder);
// now send the response back
responder.send().expect("fake disconnect response: send failed")
}
// In response to the Client SME Status request, respond based on input
// status. Status response is made up of connected (bss info) and
// connecting (to ssid). Here are the 3 supported scenarios:
// Empty Status: Both fields are empty (IF delete success)
// Connected: connected is set and connecting is null (IF deleted failed)
// Connecting: connected is null and connecting is set (IF delete failed)
fn respond_to_client_sme_status_request(
exec: &mut Executor,
req_stream: &mut ClientSmeRequestStream,
status: &StatusResponse,
) {
let req = exec.run_until_stalled(&mut req_stream.next());
let responder = assert_variant !(
req,
Poll::Ready(Some(Ok(ClientSmeRequest::Status{ responder})))
=> responder);
// Send appropriate status response
match status {
StatusResponse::Empty => {
let connected_to_bss_info = create_bssinfo_using_ssid(vec![]);
let mut response = fidl_sme::ClientStatusResponse {
connected_to: connected_to_bss_info,
connecting_to_ssid: vec![],
};
responder.send(&mut response).expect("Failed to send StatusResponse.");
}
StatusResponse::Connected => {
let connected_to_bss_info = create_bssinfo_using_ssid(vec![1, 2, 3, 4]);
let mut response = fidl_sme::ClientStatusResponse {
connected_to: connected_to_bss_info,
connecting_to_ssid: vec![],
};
responder.send(&mut response).expect("Failed to send StatusResponse.");
}
StatusResponse::Connecting => {
let connected_to_bss_info = create_bssinfo_using_ssid(vec![]);
let mut response = fidl_sme::ClientStatusResponse {
connected_to: connected_to_bss_info,
connecting_to_ssid: vec![1, 2, 3, 4],
};
responder.send(&mut response).expect("Failed to send StatusResponse.");
}
}
}
fn test_get_first_client_sme(
iface_list: &[MacRole],
) -> Result<fidl_sme::ClientSmeProxy, Error> {
let (mut exec, proxy, mut req_stream) = crate::setup_fake_service::<DeviceServiceMarker>();
let fut = get_first_client_sme(&proxy);
pin_mut!(fut);
let ifaces =
(0..iface_list.len() as u16).map(|iface_id| IfaceListItem { iface_id }).collect();
assert!(exec.run_until_stalled(&mut fut).is_pending());
respond_to_query_iface_list_request(&mut exec, &mut req_stream, ifaces);
for mac_role in iface_list {
// iface query response
assert!(exec.run_until_stalled(&mut fut).is_pending());
respond_to_query_iface_request(
&mut exec,
&mut req_stream,
*mac_role,
Some([1, 2, 3, 4, 5, 6]),
);
if *mac_role == MacRole::Client {
// client sme proxy
assert!(exec.run_until_stalled(&mut fut).is_pending());
respond_to_get_client_sme_request(&mut exec, &mut req_stream, zx::Status::OK);
break;
}
}
exec.run_singlethreaded(&mut fut)
}
fn test_disconnect_all_clients(iface_list: &[(MacRole, StatusResponse)]) -> Result<(), Error> {
let (mut exec, proxy, mut req_stream) = crate::setup_fake_service::<DeviceServiceMarker>();
let fut = disconnect_all_clients(&proxy);
pin_mut!(fut);
let ifaces =
(0..iface_list.len() as u16).map(|iface_id| IfaceListItem { iface_id }).collect();
assert!(exec.run_until_stalled(&mut fut).is_pending());
respond_to_query_iface_list_request(&mut exec, &mut req_stream, ifaces);
for (mac_role, status) in iface_list {
// iface query response
assert!(exec.run_until_stalled(&mut fut).is_pending());
respond_to_query_iface_request(
&mut exec,
&mut req_stream,
*mac_role,
Some([1, 2, 3, 4, 5, 6]),
);
if *mac_role == MacRole::Client {
// Get the Client SME server (to send the responses for the following 2 SME requests)
assert!(exec.run_until_stalled(&mut fut).is_pending());
let mut fake_sme_server_stream =
extract_sme_server_from_get_client_sme_req_and_respond(
&mut exec,
&mut req_stream,
zx::Status::OK,
);
// Disconnect
assert!(exec.run_until_stalled(&mut fut).is_pending());
respond_to_client_sme_disconnect_request(&mut exec, &mut fake_sme_server_stream);
assert!(exec.run_until_stalled(&mut fut).is_pending());
// Send appropriate status response
respond_to_client_sme_status_request(
&mut exec,
&mut fake_sme_server_stream,
status,
);
}
}
exec.run_singlethreaded(&mut fut)
}
// iface list contains an AP and a client. Test should pass
#[test]
fn check_get_client_sme_success() {
let iface_list: Vec<MacRole> = vec![MacRole::Ap, MacRole::Client];
test_get_first_client_sme(&iface_list).expect("expect success but failed");
}
// iface list is empty. Test should fail
#[test]
fn check_get_client_sme_no_devices() {
let iface_list: Vec<MacRole> = Vec::new();
test_get_first_client_sme(&iface_list).expect_err("expect fail but succeeded");
}
// iface list does not contain a client. Test should fail
#[test]
fn check_get_client_sme_no_clients() {
let iface_list: Vec<MacRole> = vec![MacRole::Ap, MacRole::Ap];
test_get_first_client_sme(&iface_list).expect_err("expect fail but succeeded");
}
// test disconnect_all_clients with a Client and an AP. Test should pass
// as AP IF will be ignored and Client IF delete should succeed.
#[test]
fn check_disconnect_all_clients_client_and_ap_success() {
let iface_list: Vec<(MacRole, StatusResponse)> =
vec![(MacRole::Ap, StatusResponse::Empty), (MacRole::Client, StatusResponse::Empty)];
test_disconnect_all_clients(&iface_list).expect("Expect success but failed")
}
// test disconnect_all_clients with 2 Clients. Test should pass as both the
// IFs are clients and both deletes should succeed.
#[test]
fn check_disconnect_all_clients_all_clients_success() {
let iface_list: Vec<(MacRole, StatusResponse)> = vec![
(MacRole::Client, StatusResponse::Empty),
(MacRole::Client, StatusResponse::Empty),
];
test_disconnect_all_clients(&iface_list).expect("Expect success but failed");
}
// test disconnect_all_clients with 2 Clients, one disconnect failure
#[test]
fn check_disconnect_all_clients_all_clients_fail() {
let iface_list: Vec<(MacRole, StatusResponse)> = vec![
(MacRole::Ap, StatusResponse::Connected),
(MacRole::Client, StatusResponse::Connected),
];
test_disconnect_all_clients(&iface_list).expect_err("Expect fail but succeeded");
}
// test disconnect_all_clients with no Clients
#[test]
fn check_disconnect_all_clients_no_clients_success() {
let iface_list: Vec<(MacRole, StatusResponse)> =
vec![(MacRole::Ap, StatusResponse::Empty), (MacRole::Ap, StatusResponse::Empty)];
test_disconnect_all_clients(&iface_list).expect("Expect success but failed");
}
#[test]
fn list_ifaces_returns_iface_id_vector() {
let mut exec = Executor::new().expect("failed to create an executor");
let (wlan_service, server) = create_wlan_service_util();
let mut next_device_service_req = server.into_future();
// create the data to use in the response
let iface_id_list: Vec<u16> = vec![0, 1, 35, 36];
let mut iface_list_vec = vec![];
for id in &iface_id_list {
iface_list_vec.push(IfaceListItem { iface_id: *id });
}
let fut = get_iface_list(&wlan_service);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_iface_list_response(&mut exec, &mut next_device_service_req, iface_list_vec);
let complete = exec.run_until_stalled(&mut fut);
let list_response = match complete {
Poll::Ready(result) => result,
_ => panic!("Expected an iface list response"),
};
let response = match list_response {
Ok(response) => response,
Err(_) => panic!("Expected a valid list response"),
};
// now verify the response
assert_eq!(response, iface_id_list)
}
#[test]
fn list_ifaces_properly_handles_zero_ifaces() {
let mut exec = Executor::new().expect("failed to create an executor");
let (wlan_service, server) = create_wlan_service_util();
let mut next_device_service_req = server.into_future();
// create the data to use in the response
let iface_id_list: Vec<u16> = vec![];
let iface_list_vec = vec![];
let fut = get_iface_list(&wlan_service);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_iface_list_response(&mut exec, &mut next_device_service_req, iface_list_vec);
let complete = exec.run_until_stalled(&mut fut);
let list_response = match complete {
Poll::Ready(result) => result,
_ => panic!("Expected an iface list response"),
};
let response = match list_response {
Ok(response) => response,
Err(_) => panic!("Expected a valid list response"),
};
// now verify the response
assert_eq!(response, iface_id_list)
}
fn poll_device_service_req(
exec: &mut Executor,
next_device_service_req: &mut StreamFuture<DeviceServiceRequestStream>,
) -> Poll<DeviceServiceRequest> {
exec.run_until_stalled(next_device_service_req).map(|(req, stream)| {
*next_device_service_req = stream.into_future();
req.expect("did not expect the DeviceServiceRequestStream to end")
.expect("error polling device service request stream")
})
}
fn send_iface_list_response(
exec: &mut Executor,
server: &mut StreamFuture<wlan_service::DeviceServiceRequestStream>,
iface_list_vec: Vec<IfaceListItem>,
) {
let responder = match poll_device_service_req(exec, server) {
Poll::Ready(DeviceServiceRequest::ListIfaces { responder }) => responder,
Poll::Pending => panic!("expected a request to be available"),
_ => panic!("expected a ListIfaces request"),
};
// now send the response back
let _result = responder.send(&mut ListIfacesResponse { ifaces: iface_list_vec });
}
#[test]
fn get_client_sme_valid_iface() {
let mut exec = Executor::new().expect("failed to create an executor");
let (wlan_service, server) = create_wlan_service_util();
let mut next_device_service_req = server.into_future();
let fut = get_iface_sme_proxy(&wlan_service, 1);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
// pass in that we expect this to succeed
send_sme_proxy_response(&mut exec, &mut next_device_service_req, zx::Status::OK);
match exec.run_until_stalled(&mut fut) {
Poll::Ready(Ok(_)) => (),
_ => panic!("Expected a status response"),
}
}
fn send_sme_proxy_response(
exec: &mut Executor,
server: &mut StreamFuture<wlan_service::DeviceServiceRequestStream>,
status: zx::Status,
) {
let responder = match poll_device_service_req(exec, server) {
Poll::Ready(DeviceServiceRequest::GetClientSme { responder, .. }) => responder,
Poll::Pending => panic!("expected a request to be available"),
_ => panic!("expected a GetClientSme request"),
};
// now send the response back
let _result = responder.send(status.into_raw());
}
#[test]
fn get_client_sme_invalid_iface() {
let mut exec = Executor::new().expect("failed to create an executor");
let (wlan_service, server) = create_wlan_service_util();
let mut next_device_service_req = server.into_future();
let fut = get_iface_sme_proxy(&wlan_service, 1);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
// pass in that we expect this to fail with zx::Status::NOT_FOUND
send_sme_proxy_response(&mut exec, &mut next_device_service_req, zx::Status::NOT_FOUND);
let complete = exec.run_until_stalled(&mut fut);
match complete {
Poll::Ready(Err(_)) => (),
_ => panic!("Expected a status response"),
};
}
#[test]
fn connect_to_network_success_returns_true() {
let connect_result = test_connect("TestAp", "", "TestAp", ConnectResultCode::Success);
assert!(connect_result);
}
#[test]
fn connect_to_network_failed_returns_false() {
let connect_result = test_connect("TestAp", "", "", ConnectResultCode::Failed);
assert!(!connect_result);
}
#[test]
fn connect_to_network_canceled_returns_false() {
let connect_result = test_connect("TestAp", "", "", ConnectResultCode::Canceled);
assert!(!connect_result);
}
#[test]
fn connect_to_network_bad_credentials_returns_false() {
let connect_result = test_connect("TestAp", "", "", ConnectResultCode::BadCredentials);
assert!(!connect_result);
}
#[test]
fn connect_to_network_different_ssid_returns_false() {
let connect_result = test_connect("TestAp1", "", "TestAp2", ConnectResultCode::Success);
assert!(!connect_result);
}
fn test_connect(
ssid: &str,
password: &str,
connected_to: &str,
result_code: ConnectResultCode,
) -> bool {
let mut exec = Executor::new().expect("failed to create an executor");
let (client_sme, server) = create_client_sme_proxy();
let mut next_client_sme_req = server.into_future();
let target_ssid = ssid.as_bytes();
let target_password = password.as_bytes();
let fut = connect_to_network(&client_sme, target_ssid.to_vec(), target_password.to_vec());
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
// have the request, need to send a response
send_connect_request_response(
&mut exec,
&mut next_client_sme_req,
target_ssid,
credential_from_password(target_password.to_vec()),
result_code,
);
// if connection is successful, status is requested to extract ssid
if result_code == ConnectResultCode::Success {
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_status_response(
&mut exec,
&mut next_client_sme_req,
connected_to.as_bytes().to_vec(),
target_ssid.to_vec(),
);
}
let complete = exec.run_until_stalled(&mut fut);
let connection_result = match complete {
Poll::Ready(result) => result,
_ => panic!("Expected a connect response"),
};
let returned_bool = match connection_result {
Ok(response) => response,
_ => panic!("Expected a valid connection result"),
};
returned_bool
}
#[test]
fn connect_to_network_properly_passes_network_info_with_password() {
let mut exec = Executor::new().expect("failed to create an executor");
let (client_sme, server) = create_client_sme_proxy();
let mut next_client_sme_req = server.into_future();
let target_ssid = "TestAp".as_bytes();
let target_password = "password".as_bytes();
let fut = connect_to_network(&client_sme, target_ssid.to_vec(), target_password.to_vec());
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
// verify the connect request info
verify_connect_request_info(
&mut exec,
&mut next_client_sme_req,
target_ssid,
credential_from_password(target_password.to_vec()),
);
}
#[test]
fn connect_to_network_properly_passes_network_info_open() {
let mut exec = Executor::new().expect("failed to create an executor");
let (client_sme, server) = create_client_sme_proxy();
let mut next_client_sme_req = server.into_future();
let target_ssid = "TestAp".as_bytes();
let target_password = "".as_bytes();
let fut = connect_to_network(&client_sme, target_ssid.to_vec(), target_password.to_vec());
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
// verify the connect request info
verify_connect_request_info(
&mut exec,
&mut next_client_sme_req,
target_ssid,
credential_from_password(vec![]),
);
}
fn verify_connect_request_info(
exec: &mut Executor,
server: &mut StreamFuture<ClientSmeRequestStream>,
expected_ssid: &[u8],
expected_credential: fidl_sme::Credential,
) {
match poll_client_sme_request(exec, server) {
Poll::Ready(ClientSmeRequest::Connect { req, .. }) => {
assert_eq!(expected_ssid, &req.ssid[..]);
assert_eq_credentials(&req.credential, &expected_credential);
}
_ => panic!("expected a Connect request"),
}
}
fn send_connect_request_response(
exec: &mut Executor,
server: &mut StreamFuture<ClientSmeRequestStream>,
expected_ssid: &[u8],
expected_credential: fidl_sme::Credential,
connect_result: ConnectResultCode,
) {
let responder = match poll_client_sme_request(exec, server) {
Poll::Ready(ClientSmeRequest::Connect { req, txn, .. }) => {
assert_eq!(expected_ssid, &req.ssid[..]);
assert_eq_credentials(&req.credential, &expected_credential);
txn.expect("expected a Connect transaction channel")
}
Poll::Pending => panic!("expected a request to be available"),
_ => panic!("expected a Connect request"),
};
let connect_transaction = responder
.into_stream()
.expect("failed to create a connect transaction stream")
.control_handle();
connect_transaction
.send_on_finished(connect_result)
.expect("failed to send OnFinished to ConnectTransaction");
}
fn poll_client_sme_request(
exec: &mut Executor,
next_client_sme_req: &mut StreamFuture<ClientSmeRequestStream>,
) -> Poll<ClientSmeRequest> {
exec.run_until_stalled(next_client_sme_req).map(|(req, stream)| {
*next_client_sme_req = stream.into_future();
req.expect("did not expect the ClientSmeRequestStream to end")
.expect("error polling client sme request stream")
})
}
fn create_client_sme_proxy() -> (fidl_sme::ClientSmeProxy, ClientSmeRequestStream) {
let (proxy, server) = endpoints::create_proxy::<ClientSmeMarker>()
.expect("failed to create sme client channel");
let server = server.into_stream().expect("failed to create a client sme response stream");
(proxy, server)
}
fn create_wlan_service_util() -> (DeviceServiceProxy, DeviceServiceRequestStream) {
let (proxy, server) = endpoints::create_proxy::<DeviceServiceMarker>()
.expect("failed to create a wlan_service channel for tests");
let server = server.into_stream().expect("failed to create a wlan_service response stream");
(proxy, server)
}
enum StatusResponse {
Empty,
Connected,
Connecting,
}
#[test]
fn disconnect_with_empty_status_response() {
if let Poll::Ready(result) = test_disconnect(StatusResponse::Empty) {
return assert!(result.is_ok());
}
panic!("disconnect did not return a Poll::Ready")
}
#[test]
fn disconnect_fail_because_connected() {
if let Poll::Ready(result) = test_disconnect(StatusResponse::Connected) {
return assert!(result.is_err());
}
panic!("disconnect did not return a Poll::Ready")
}
#[test]
fn disconnect_fail_because_connecting() {
if let Poll::Ready(result) = test_disconnect(StatusResponse::Connecting) {
return assert!(result.is_err());
}
panic!("disconnect did not return a Poll::Ready")
}
fn test_disconnect(status: StatusResponse) -> Poll<Result<(), Error>> {
let mut exec = Executor::new().expect("failed to create an executor");
let (client_sme, server) = create_client_sme_proxy();
let mut client_sme_req = server.into_future();
let fut = disconnect_from_network(&client_sme);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_disconnect_request_response(&mut exec, &mut client_sme_req);
assert!(exec.run_until_stalled(&mut fut).is_pending());
match status {
StatusResponse::Empty => {
send_status_response(&mut exec, &mut client_sme_req, vec![], vec![])
}
StatusResponse::Connected => {
send_status_response(&mut exec, &mut client_sme_req, vec![1, 2, 3, 4], vec![])
}
StatusResponse::Connecting => {
send_status_response(&mut exec, &mut client_sme_req, vec![], vec![1, 2, 3, 4])
}
}
exec.run_until_stalled(&mut fut)
}
fn send_disconnect_request_response(
exec: &mut Executor,
server: &mut StreamFuture<ClientSmeRequestStream>,
) {
let rsp = match poll_client_sme_request(exec, server) {
Poll::Ready(ClientSmeRequest::Disconnect { responder }) => responder,
Poll::Pending => panic!("Expected a DisconnectRequest"),
_ => panic!("Expected a DisconnectRequest"),
};
rsp.send().expect("Failed to send DisconnectResponse.");
}
fn create_bssinfo_using_ssid(ssid: Vec<u8>) -> Option<Box<BssInfo>> {
match ssid.is_empty() {
true => None,
_ => {
let bss_info: fidl_sme::BssInfo = fidl_sme::BssInfo {
bssid: [0, 1, 2, 3, 4, 5],
ssid: ssid,
rx_dbm: -30,
channel: 1,
protection: Protection::Wpa2Personal,
compatible: true,
};
Some(Box::new(bss_info))
}
}
}
fn send_status_response(
exec: &mut Executor,
server: &mut StreamFuture<ClientSmeRequestStream>,
connected_to: Vec<u8>,
connecting_to_ssid: Vec<u8>,
) {
let rsp = match poll_client_sme_request(exec, server) {
Poll::Ready(ClientSmeRequest::Status { responder }) => responder,
Poll::Pending => panic!("Expected a StatusRequest"),
_ => panic!("Expected a StatusRequest"),
};
let connected_to_bss_info = create_bssinfo_using_ssid(connected_to);
let mut response = fidl_sme::ClientStatusResponse {
connected_to: connected_to_bss_info,
connecting_to_ssid: connecting_to_ssid,
};
rsp.send(&mut response).expect("Failed to send StatusResponse.");
}
#[test]
fn scan_success_returns_empty_results() {
let scan_results_for_response = Vec::new();
let scan_results = test_perform_scan(scan_results_for_response);
assert_eq!(scan_results, Vec::new());
}
#[test]
fn scan_success_returns_results() {
let mut scan_results_for_response = Vec::new();
// due to restrictions for cloning fidl objects, forced to make a copy of the vector here
let entry1 = create_bss_info(
[0, 1, 2, 3, 4, 5],
b"foo".to_vec(),
-30,
1,
Protection::Wpa2Personal,
true,
);
let entry1_copy = fidl_sme::BssInfo { ssid: entry1.ssid.clone(), ..entry1 };
let entry2 = create_bss_info(
[1, 2, 3, 4, 5, 6],
b"hello".to_vec(),
-60,
2,
Protection::Wpa2Personal,
false,
);
let entry2_copy = fidl_sme::BssInfo { ssid: entry2.ssid.clone(), ..entry2 };
scan_results_for_response.push(entry1);
scan_results_for_response.push(entry2);
let mut expected_response = Vec::new();
expected_response.push(entry1_copy);
expected_response.push(entry2_copy);
let scan_results = test_perform_scan(scan_results_for_response);
assert_eq!(scan_results, expected_response);
}
#[test]
fn scan_error_correctly_handled() {
// need to expect an error
assert!(test_perform_scan_error().is_err())
}
fn test_perform_scan(mut scan_results: Vec<fidl_sme::BssInfo>) -> Vec<fidl_sme::BssInfo> {
let mut exec = Executor::new().expect("failed to create an executor");
let (client_sme, server) = create_client_sme_proxy();
let mut client_sme_req = server.into_future();
let fut = perform_scan(&client_sme);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_scan_result_response(&mut exec, &mut client_sme_req, &mut scan_results);
let complete = exec.run_until_stalled(&mut fut);
let request_result = match complete {
Poll::Ready(result) => result,
_ => panic!("Expected a scan request result"),
};
let returned_scan_results = request_result.expect("failed to get scan results");
returned_scan_results
}
fn send_scan_result_response(
exec: &mut Executor,
server: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>,
scan_results: &mut Vec<fidl_sme::BssInfo>,
) {
let transaction = match poll_client_sme_request(exec, server) {
Poll::Ready(fidl_sme::ClientSmeRequest::Scan { txn, .. }) => txn,
Poll::Pending => panic!("expected a request to be available"),
_ => panic!("expected a scan request"),
};
// now send the response back
let transaction = transaction
.into_stream()
.expect("failed to create a scan transaction stream")
.control_handle();
transaction
.send_on_result(&mut scan_results.into_iter())
.expect("failed to send scan results");
transaction.send_on_finished().expect("failed to send OnFinished to ScanTransaction");
}
fn test_perform_scan_error() -> Result<(), Error> {
let mut exec = Executor::new().expect("failed to create an executor");
let (client_sme, server) = create_client_sme_proxy();
let mut client_sme_req = server.into_future();
let fut = perform_scan(&client_sme);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_scan_error_response(&mut exec, &mut client_sme_req);
let _ = exec.run_until_stalled(&mut fut)?;
Ok(())
}
fn send_scan_error_response(
exec: &mut Executor,
server: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>,
) {
let transaction = match poll_client_sme_request(exec, server) {
Poll::Ready(fidl_sme::ClientSmeRequest::Scan { txn, .. }) => txn,
Poll::Pending => panic!("expected a request to be available"),
_ => panic!("expected a scan request"),
};
// create error to send back
let mut scan_error = fidl_sme::ScanError {
code: fidl_sme::ScanErrorCode::InternalError,
message: "Scan error".to_string(),
};
// now send the response back
let transaction = transaction
.into_stream()
.expect("failed to create a scan transaction stream")
.control_handle();
transaction.send_on_error(&mut scan_error).expect("failed to send ScanError");
}
fn create_bss_info(
bssid: [u8; 6],
ssid: Vec<u8>,
rx_dbm: i8,
channel: u8,
protection: Protection,
compatible: bool,
) -> fidl_sme::BssInfo {
fidl_sme::BssInfo { bssid, ssid, rx_dbm, channel, protection, compatible }
}
fn assert_eq_credentials(
actual_credential: &fidl_sme::Credential,
expected_credential: &fidl_sme::Credential,
) {
match actual_credential {
fidl_sme::Credential::Password(password) => match expected_credential {
fidl_sme::Credential::Password(expected_password) => {
assert_eq!(&expected_password[..], &password[..]);
}
expected => panic!("got password, expected: {:?}", expected),
},
fidl_sme::Credential::None(_) => match expected_credential {
fidl_sme::Credential::None(_) => (),
expected => panic!("got no password, expected: {:?}", expected),
},
unsupported => panic!("unsupported credential type: {:?}", unsupported),
}
}
fn respond_to_query_iface_request(
exec: &mut Executor,
req_stream: &mut DeviceServiceRequestStream,
role: fidl_fuchsia_wlan_device::MacRole,
fake_mac_addr: Option<[u8; 6]>,
) {
use fuchsia_zircon::sys::{ZX_ERR_NOT_FOUND, ZX_OK};
let req = exec.run_until_stalled(&mut req_stream.next());
let responder = assert_variant !(
req,
Poll::Ready(Some(Ok(DeviceServiceRequest::QueryIface{iface_id : _, responder})))
=> responder);
if let Some(mac) = fake_mac_addr {
let mut response = fake_iface_query_response(mac, role);
responder
.send(ZX_OK, Some(&mut response))
.expect("sending fake response with mac address");
} else {
responder.send(ZX_ERR_NOT_FOUND, None).expect("sending fake response with none")
}
}
fn fake_iface_query_response(
mac_addr: [u8; 6],
role: fidl_fuchsia_wlan_device::MacRole,
) -> QueryIfaceResponse {
QueryIfaceResponse { role, id: 0, phy_id: 0, phy_assigned_id: 0, mac_addr }
}
#[test]
fn test_get_wlan_mac_addr_ok() {
let (mut exec, proxy, mut req_stream) = crate::setup_fake_service::<DeviceServiceMarker>();
let mac_addr_fut = get_wlan_mac_addr(&proxy, 0);
pin_mut!(mac_addr_fut);
assert_variant!(exec.run_until_stalled(&mut mac_addr_fut), Poll::Pending);
respond_to_query_iface_request(
&mut exec,
&mut req_stream,
MacRole::Client,
Some([1, 2, 3, 4, 5, 6]),
);
let mac_addr = exec.run_singlethreaded(&mut mac_addr_fut).expect("should get a mac addr");
assert_eq!(mac_addr, [1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_get_wlan_mac_addr_not_found() {
let (mut exec, proxy, mut req_stream) = crate::setup_fake_service::<DeviceServiceMarker>();
let mac_addr_fut = get_wlan_mac_addr(&proxy, 0);
pin_mut!(mac_addr_fut);
assert_variant!(exec.run_until_stalled(&mut mac_addr_fut), Poll::Pending);
respond_to_query_iface_request(&mut exec, &mut req_stream, MacRole::Client, None);
let err = exec.run_singlethreaded(&mut mac_addr_fut).expect_err("should be an error");
assert_eq!("No valid iface response", format!("{}", err));
}
#[test]
fn test_get_wlan_mac_addr_service_interrupted() {
let (mut exec, proxy, req_stream) = crate::setup_fake_service::<DeviceServiceMarker>();
let mac_addr_fut = get_wlan_mac_addr(&proxy, 0);
pin_mut!(mac_addr_fut);
assert_variant!(exec.run_until_stalled(&mut mac_addr_fut), Poll::Pending);
// Simulate service not being available by closing the channel
std::mem::drop(req_stream);
let err = exec.run_singlethreaded(&mut mac_addr_fut).expect_err("should be an error");
assert!(format!("{}", err).contains("PEER_CLOSED"));
}
fn send_destroy_iface_response(
exec: &mut Executor,
server: &mut StreamFuture<wlan_service::DeviceServiceRequestStream>,
status: zx::Status,
) {
let responder = match poll_device_service_req(exec, server) {
Poll::Ready(DeviceServiceRequest::DestroyIface { responder, .. }) => responder,
Poll::Pending => panic!("expected a request to be available"),
_ => panic!("expected a destroy iface request"),
};
// now send the response back
let _result = responder.send(status.into_raw());
}
#[test]
fn test_destroy_single_iface_ok() {
let mut exec = Executor::new().expect("failed to create an executor");
let (wlan_service, server) = create_wlan_service_util();
let mut next_device_service_req = server.into_future();
let fut = destroy_iface(&wlan_service, 0);
pin_mut!(fut);
assert!(exec.run_until_stalled(&mut fut).is_pending());
send_destroy_iface_response(&mut exec, &mut next_device_service_req, zx::Status::OK);
match exec.run_until_stalled(&mut fut) {
Poll::Ready(Ok(_)) => (),
_ => panic!("Expected a status response"),
};
}
}