blob: 1bdb7c88ad088713f5d24e1058c59ee4671588e5 [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 fidl_fuchsia_wlan_mlme::{self as fidl_mlme, BssDescription, MlmeEvent};
use log::{error, warn};
use wlan_rsn::key::exchange::Key;
use wlan_rsn::rsna::{self, SecAssocUpdate, SecAssocStatus};
use super::bss::convert_bss_description;
use super::{ConnectFailure, ConnectPhyParams, ConnectResult, InfoEvent, Status, Tokens};
use super::rsn::Rsna;
use crate::MlmeRequest;
use crate::client::{Context, event::{self, Event}, report_connect_finished};
use crate::clone_utils::clone_bss_desc;
use crate::phy_selection::{derive_phy_cbw};
use crate::sink::MlmeSink;
use crate::timer::EventId;
const DEFAULT_JOIN_FAILURE_TIMEOUT: u32 = 20; // beacon intervals
const DEFAULT_AUTH_FAILURE_TIMEOUT: u32 = 20; // beacon intervals
#[derive(Debug, PartialEq)]
pub enum LinkState<T: Tokens> {
EstablishingRsna {
token: Option<T::ConnectToken>,
rsna: Rsna,
// Timeout for the total duration RSNA may take to complete.
rsna_timeout: Option<EventId>,
// Timeout waiting to receive a key frame from the Authenticator. This timeout is None at
// the beginning of the RSNA when no frame has been exchanged yet, or at the end of the
// RSNA when all the key frames have finished exchanging.
resp_timeout: Option<EventId>,
},
LinkUp(Option<Rsna>)
}
#[derive(Debug, PartialEq)]
pub struct ConnectCommand<T> {
pub bss: Box<BssDescription>,
pub token: Option<T>,
pub rsna: Option<Rsna>,
pub params: ConnectPhyParams
}
#[derive(Debug)]
pub enum RsnaStatus {
Established,
Failed(ConnectResult),
Unchanged,
Progressed {
new_resp_timeout: Option<EventId>,
},
}
#[derive(Debug, PartialEq)]
pub enum State<T: Tokens> {
Idle,
Joining {
cmd: ConnectCommand<T::ConnectToken>,
},
Authenticating {
cmd: ConnectCommand<T::ConnectToken>,
},
Associating {
cmd: ConnectCommand<T::ConnectToken>,
},
Associated {
bss: Box<BssDescription>,
last_rssi: Option<i8>,
link_state: LinkState<T>,
params: ConnectPhyParams,
},
}
impl<T: Tokens> State<T> {
pub fn on_mlme_event(self, event: MlmeEvent, context: &mut Context<T>) -> Self {
match self {
State::Idle => {
warn!("Unexpected MLME message while Idle: {:?}", event);
State::Idle
},
State::Joining{ cmd } => match event {
MlmeEvent::JoinConf { resp } => match resp.result_code {
fidl_mlme::JoinResultCodes::Success => {
context.mlme_sink.send(MlmeRequest::Authenticate(
fidl_mlme::AuthenticateRequest {
peer_sta_address: cmd.bss.bssid.clone(),
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
auth_failure_timeout: DEFAULT_AUTH_FAILURE_TIMEOUT,
}));
State::Authenticating { cmd }
},
other => {
error!("Join request failed with result code {:?}", other);
report_connect_finished(cmd.token, &context, ConnectResult::Failed,
Some(ConnectFailure::JoinFailure(other)),
);
State::Idle
}
},
_ => {
State::Joining{ cmd }
}
},
State::Authenticating{ cmd } => match event {
MlmeEvent::AuthenticateConf { resp } => match resp.result_code {
fidl_mlme::AuthenticateResultCodes::Success => {
to_associating_state(cmd, &context.mlme_sink)
},
other => {
error!("Authenticate request failed with result code {:?}", other);
report_connect_finished(cmd.token, &context, ConnectResult::Failed,
Some(ConnectFailure::AuthenticationFailure(other)),
);
State::Idle
}
},
_ => State::Authenticating{ cmd }
},
State::Associating{ cmd } => match event {
MlmeEvent::AssociateConf { resp } => match resp.result_code {
fidl_mlme::AssociateResultCodes::Success => {
context.info_sink.send(
InfoEvent::AssociationSuccess { att_id: context.att_id });
match cmd.rsna {
Some(mut rsna) => match rsna.supplicant.start() {
Err(e) => {
handle_supplicant_start_failure(cmd.token, cmd.bss,
&context, e);
State::Idle
},
Ok(_) => {
context.info_sink.send(
InfoEvent::RsnaStarted { att_id: context.att_id });
let rsna_timeout = Some(context.timer.schedule(
Event::EstablishingRsnaTimeout));
State::Associated {
bss: cmd.bss,
last_rssi: None,
link_state: LinkState::EstablishingRsna {
token: cmd.token,
rsna,
rsna_timeout,
resp_timeout: None,
},
params: cmd.params,
}
}
},
None => {
report_connect_finished(cmd.token, &context,
ConnectResult::Success, None);
State::Associated {
bss: cmd.bss,
last_rssi: None,
link_state: LinkState::LinkUp(None),
params: cmd.params,
}
}
}
},
other => {
error!("Associate request failed with result code {:?}", other);
report_connect_finished(cmd.token, &context, ConnectResult::Failed,
Some(ConnectFailure::AssociationFailure(other)),
);
State::Idle
}
},
_ => State::Associating{ cmd }
},
State::Associated { bss, last_rssi, link_state, params, } => match event {
MlmeEvent::DisassociateInd{ .. } => {
let (token, mut rsna) = match link_state {
LinkState::LinkUp(rsna) => (None, rsna),
LinkState::EstablishingRsna{ token, rsna, .. } => (token, Some(rsna)),
};
// Client is disassociating. The ESS-SA must be kept alive but reset.
if let Some(rsna) = &mut rsna {
rsna.supplicant.reset();
}
let cmd = ConnectCommand{
bss,
token,
rsna,
params,
};
context.att_id += 1;
to_associating_state(cmd, &context.mlme_sink)
},
MlmeEvent::DeauthenticateInd{ ind } => {
if let LinkState::EstablishingRsna{ token, .. } = link_state {
let connect_result = deauth_code_to_connect_result(ind.reason_code);
report_connect_finished(token, &context, connect_result, None);
}
State::Idle
},
MlmeEvent::SignalReport{ ind } => {
State::Associated {
bss,
last_rssi: Some(ind.rssi_dbm),
link_state,
params,
}
},
MlmeEvent::EapolInd{ ref ind } if bss.rsn.is_some() => match link_state {
LinkState::EstablishingRsna{ token, mut rsna, rsna_timeout,
mut resp_timeout } => {
match process_eapol_ind(context, &mut rsna, &ind) {
RsnaStatus::Established => {
context.mlme_sink.send(MlmeRequest::SetCtrlPort(
fidl_mlme::SetControlledPortRequest {
peer_sta_address: bss.bssid.clone(),
state: fidl_mlme::ControlledPortState::Open,
}
));
context.info_sink.send(
InfoEvent::RsnaEstablished { att_id: context.att_id });
report_connect_finished(token, &context,
ConnectResult::Success, None);
let link_state = LinkState::LinkUp(Some(rsna));
State::Associated { bss, last_rssi, link_state, params, }
},
RsnaStatus::Failed(result) => {
report_connect_finished(token, &context, result, None);
send_deauthenticate_request(bss, &context.mlme_sink);
State::Idle
},
RsnaStatus::Unchanged => {
let link_state = LinkState::EstablishingRsna {
token, rsna, rsna_timeout, resp_timeout };
State::Associated { bss, last_rssi, link_state, params, }
},
RsnaStatus::Progressed { new_resp_timeout } => {
cancel(&mut resp_timeout);
if let Some(id) = new_resp_timeout {
resp_timeout.replace(id);
}
let link_state = LinkState::EstablishingRsna {
token, rsna, rsna_timeout, resp_timeout};
State::Associated { bss, last_rssi, link_state, params, }
}
}
},
LinkState::LinkUp(Some(mut rsna)) => {
match process_eapol_ind(context, &mut rsna, &ind) {
RsnaStatus::Unchanged => {},
// Once re-keying is supported, the RSNA can fail in LinkUp as well
// and cause deauthentication.
s => error!("unexpected RsnaStatus in LinkUp state: {:?}", s),
};
let link_state = LinkState::LinkUp(Some(rsna));
State::Associated { bss, last_rssi, link_state, params, }
},
_ => panic!("expected Link to carry RSNA because bss.rsn is present"),
}
_ => State::Associated{ bss, last_rssi, link_state, params, }
},
}
}
pub fn handle_timeout(self, event_id: EventId, event: Event, context: &mut Context<T>) -> Self {
match self {
State::Associated { bss, last_rssi, link_state, params } => match link_state {
LinkState::EstablishingRsna { token, rsna, mut rsna_timeout, mut resp_timeout } => {
match event {
Event::EstablishingRsnaTimeout if triggered(&rsna_timeout,
event_id) => {
error!("timeout establishing RSNA; deauthenticating");
cancel(&mut rsna_timeout);
report_connect_finished(token, &context, ConnectResult::Failed,
Some(ConnectFailure::RsnaTimeout));
send_deauthenticate_request(bss, &context.mlme_sink);
State::Idle
},
Event::KeyFrameExchangeTimeout { bssid, sta_addr, frame, attempt } => {
if !triggered(&resp_timeout, event_id) {
let link_state = LinkState::EstablishingRsna {
token, rsna, rsna_timeout, resp_timeout, };
return State::Associated { bss, last_rssi, link_state, params }
}
if attempt < event::KEY_FRAME_EXCHANGE_MAX_ATTEMPTS {
warn!("timeout waiting for key frame for attempt {}; retrying",
attempt);
let id = send_eapol_frame(context, bssid, sta_addr, frame,
attempt + 1);
resp_timeout.replace(id);
let link_state = LinkState::EstablishingRsna {
token, rsna, rsna_timeout, resp_timeout, };
State::Associated { bss, last_rssi, link_state, params }
} else {
error!("timeout waiting for key frame for last attempt; deauth");
cancel(&mut resp_timeout);
report_connect_finished(token, &context, ConnectResult::Failed,
Some(ConnectFailure::RsnaTimeout));
send_deauthenticate_request(bss, &context.mlme_sink);
State::Idle
}
},
_ => {
let link_state = LinkState::EstablishingRsna {
token, rsna, rsna_timeout, resp_timeout };
State::Associated { bss, last_rssi, link_state, params }
},
}
},
_ => State::Associated { bss, last_rssi, link_state, params },
},
_ => self,
}
}
pub fn connect(self, cmd: ConnectCommand<T::ConnectToken>, context: &mut Context<T>) -> Self {
self.disconnect_internal(context);
let mut selected_bss = clone_bss_desc(&cmd.bss);
let (phy_to_use, cbw_to_use)
= derive_phy_cbw(&selected_bss, &context.device_info, &cmd.params);
selected_bss.chan.cbw = cbw_to_use;
context.mlme_sink.send(MlmeRequest::Join(
fidl_mlme::JoinRequest {
selected_bss,
join_failure_timeout: DEFAULT_JOIN_FAILURE_TIMEOUT,
nav_sync_delay: 0,
op_rate_set: vec![],
phy: phy_to_use,
cbw: cbw_to_use,
}
));
context.att_id += 1;
context.info_sink.send(InfoEvent::AssociationStarted { att_id: context.att_id });
State::Joining { cmd }
}
pub fn disconnect(self, context: &Context<T>) -> Self {
self.disconnect_internal(context);
State::Idle
}
fn disconnect_internal(self, context: &Context<T>) {
match self {
State::Idle => {},
State::Joining { cmd } | State::Authenticating { cmd } => {
report_connect_finished(cmd.token, &context, ConnectResult::Canceled, None);
},
State::Associating{ cmd, .. } => {
report_connect_finished(cmd.token, &context, ConnectResult::Canceled, None);
send_deauthenticate_request(cmd.bss, &context.mlme_sink);
},
State::Associated { bss, .. } => {
send_deauthenticate_request(bss, &context.mlme_sink);
},
}
}
pub fn status(&self) -> Status {
match self {
State::Idle => Status {
connected_to: None,
connecting_to: None,
},
State::Joining { cmd }
| State::Authenticating { cmd }
| State::Associating { cmd, .. } =>
{
Status {
connected_to: None,
connecting_to: Some(cmd.bss.ssid.clone()),
}
},
State::Associated { bss, link_state: LinkState::EstablishingRsna { .. }, .. } => {
Status {
connected_to: None,
connecting_to: Some(bss.ssid.clone()),
}
},
State::Associated { bss, link_state: LinkState::LinkUp(..), .. } => Status {
connected_to: Some(convert_bss_description(bss)),
connecting_to: None,
},
}
}
}
fn triggered(id: &Option<EventId>, received_id: EventId) -> bool {
id.map_or(false, |id| id == received_id)
}
fn cancel(event_id: &mut Option<EventId>) {
let _ = event_id.take();
}
fn deauth_code_to_connect_result(reason_code: fidl_mlme::ReasonCode) -> ConnectResult {
match reason_code {
fidl_mlme::ReasonCode::InvalidAuthentication
| fidl_mlme::ReasonCode::Ieee8021XAuthFailed => ConnectResult::BadCredentials,
_ => ConnectResult::Failed
}
}
fn process_eapol_ind<T: Tokens>(context: &mut Context<T>, rsna: &mut Rsna,
ind: &fidl_mlme::EapolIndication)
-> RsnaStatus
{
let mic_size = rsna.negotiated_rsne.mic_size;
let eapol_pdu = &ind.data[..];
let eapol_frame = match eapol::key_frame_from_bytes(eapol_pdu, mic_size).to_full_result() {
Ok(key_frame) => eapol::Frame::Key(key_frame),
Err(e) => {
error!("received invalid EAPOL Key frame: {:?}", e);
return RsnaStatus::Unchanged;
}
};
let mut update_sink = rsna::UpdateSink::default();
match rsna.supplicant.on_eapol_frame(&mut update_sink, &eapol_frame) {
Err(e) => {
error!("error processing EAPOL key frame: {}", e);
return RsnaStatus::Unchanged;
}
Ok(_) if update_sink.is_empty() => return RsnaStatus::Unchanged,
_ => (),
}
let bssid = ind.src_addr;
let sta_addr = ind.dst_addr;
let mut new_resp_timeout = None;
for update in update_sink {
match update {
// ESS Security Association requests to send an EAPOL frame.
// Forward EAPOL frame to MLME.
SecAssocUpdate::TxEapolKeyFrame(frame) => {
new_resp_timeout.replace(send_eapol_frame(context, bssid, sta_addr, frame, 1));
},
// ESS Security Association derived a new key.
// Configure key in MLME.
SecAssocUpdate::Key(key) => {
send_keys(&context.mlme_sink, bssid, key)
},
// Received a status update.
// TODO(hahnr): Rework this part.
// As of now, we depend on the fact that the status is always the last update.
// However, this fact is not clear from the API.
// We should fix the API and make this more explicit.
// Then we should rework this part.
SecAssocUpdate::Status(status) => match status {
// ESS Security Association was successfully established. Link is now up.
SecAssocStatus::EssSaEstablished => return RsnaStatus::Established,
// TODO(hahnr): The API should not expose whether or not the connection failed
// because of bad credentials as it allows callers to reason about location
// information since the network was apparently found.
SecAssocStatus::WrongPassword => {
return RsnaStatus::Failed(ConnectResult::BadCredentials);
}
},
}
}
RsnaStatus::Progressed { new_resp_timeout }
}
fn send_eapol_frame<T: Tokens>(context: &mut Context<T>, bssid: [u8; 6], sta_addr: [u8; 6],
frame: eapol::KeyFrame, attempt: u32)
-> EventId
{
let resp_timeout_id = context.timer.schedule(Event::KeyFrameExchangeTimeout {
bssid,
sta_addr,
frame: frame.clone(),
attempt
});
let mut buf = Vec::with_capacity(frame.len());
frame.as_bytes(false, &mut buf);
context.mlme_sink.send(MlmeRequest::Eapol(
fidl_mlme::EapolRequest {
src_addr: sta_addr,
dst_addr: bssid,
data: buf,
}
));
resp_timeout_id
}
fn send_keys(mlme_sink: &MlmeSink, bssid: [u8; 6], key: Key)
{
match key {
Key::Ptk(ptk) => {
mlme_sink.send(MlmeRequest::SetKeys(
fidl_mlme::SetKeysRequest {
keylist: vec![fidl_mlme::SetKeyDescriptor{
key_type: fidl_mlme::KeyType::Pairwise,
key: ptk.tk().to_vec(),
key_id: 0,
address: bssid,
cipher_suite_oui: eapol::to_array(&ptk.cipher.oui[..]),
cipher_suite_type: ptk.cipher.suite_type,
rsc: [0u8; 8],
}]
}
));
},
Key::Gtk(gtk) => {
mlme_sink.send(MlmeRequest::SetKeys(
fidl_mlme::SetKeysRequest {
keylist: vec![fidl_mlme::SetKeyDescriptor{
key_type: fidl_mlme::KeyType::Group,
key: gtk.tk().to_vec(),
key_id: gtk.key_id() as u16,
address: [0xFFu8; 6],
cipher_suite_oui: eapol::to_array(&gtk.cipher.oui[..]),
cipher_suite_type: gtk.cipher.suite_type,
rsc: [0u8; 8],
}]
}
));
},
_ => error!("derived unexpected key")
};
}
fn send_deauthenticate_request(current_bss: Box<BssDescription>,
mlme_sink: &MlmeSink) {
mlme_sink.send(MlmeRequest::Deauthenticate(
fidl_mlme::DeauthenticateRequest {
peer_sta_address: current_bss.bssid.clone(),
reason_code: fidl_mlme::ReasonCode::StaLeaving,
}
));
}
fn to_associating_state<T>(cmd: ConnectCommand<T::ConnectToken>, mlme_sink: &MlmeSink)
-> State<T>
where T: Tokens
{
let s_rsne_data = cmd.rsna.as_ref().map(|rsna| {
let s_rsne = rsna.negotiated_rsne.to_full_rsne();
let mut buf = Vec::with_capacity(s_rsne.len());
s_rsne.as_bytes(&mut buf);
buf
});
mlme_sink.send(MlmeRequest::Associate(
fidl_mlme::AssociateRequest {
peer_sta_address: cmd.bss.bssid.clone(),
rsn: s_rsne_data,
}
));
State::Associating { cmd }
}
fn handle_supplicant_start_failure<T>(token: Option<T::ConnectToken>, bss: Box<BssDescription>,
context: &Context<T>, e: failure::Error) where T: Tokens
{
error!("deauthenticating; could not start Supplicant: {}", e);
send_deauthenticate_request(bss, &context.mlme_sink);
// TODO(hahnr): Report RSNA specific failure instead.
let reason = fidl_mlme::AssociateResultCodes::RefusedReasonUnspecified;
report_connect_finished(token, &context,
ConnectResult::Failed,
Some(ConnectFailure::AssociationFailure(reason)));
}
#[cfg(test)]
mod tests {
use super::*;
use failure::format_err;
use futures::channel::mpsc;
use std::error::Error;
use std::sync::Arc;
use wlan_rsn::{NegotiatedRsne, rsna::UpdateSink, rsne::RsnCapabilities};
use crate::client::test_utils::{
expect_info_event,
fake_protected_bss_description,
fake_unprotected_bss_description,
mock_supplicant,
MockSupplicant,
MockSupplicantController,
};
use crate::client::{InfoSink, TimeStream, UserEvent, UserStream, UserSink};
use crate::{DeviceInfo, InfoStream, MlmeStream, Ssid, test_utils, timer};
#[derive(Debug, PartialEq)]
struct FakeTokens;
impl Tokens for FakeTokens {
type ScanToken = u32;
type ConnectToken = u32;
}
#[test]
fn associate_happy_path_unprotected() {
let mut h = TestHelper::new();
let state = idle_state();
let command = connect_command_one();
let bss_ssid = command.bss.ssid.clone();
let bssid = command.bss.bssid.clone();
let command_token = command.token.unwrap();
// Issue a "connect" command
let state = state.connect(command, &mut h.context);
expect_info_event(&mut h.info_stream, InfoEvent::AssociationStarted { att_id: 1});
expect_join_request(&mut h.mlme_stream, &bss_ssid);
// (mlme->sme) Send a JoinConf as a response
let join_conf = create_join_conf(fidl_mlme::JoinResultCodes::Success);
let state = state.on_mlme_event(join_conf, &mut h.context);
expect_auth_req(&mut h.mlme_stream, bssid);
// (mlme->sme) Send an AuthenticateConf as a response
let auth_conf = create_auth_conf(bssid.clone(),
fidl_mlme::AuthenticateResultCodes::Success);
let state = state.on_mlme_event(auth_conf, &mut h.context);
expect_assoc_req(&mut h.mlme_stream, bssid);
// (mlme->sme) Send an AssociateConf
let assoc_conf = create_assoc_conf(fidl_mlme::AssociateResultCodes::Success);
let _state = state.on_mlme_event(assoc_conf, &mut h.context);
// User should be notified that we are connected
expect_connect_result(&mut h.user_stream, command_token, ConnectResult::Success);
expect_info_event(&mut h.info_stream, InfoEvent::AssociationSuccess { att_id: 1 });
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished { result: ConnectResult::Success, failure: None });
}
#[test]
fn associate_happy_path_protected() {
let mut h = TestHelper::new();
let (supplicant, suppl_mock) = mock_supplicant();
let state = idle_state();
let command = connect_command_rsna(supplicant);
let bss_ssid = command.bss.ssid.clone();
let bssid = command.bss.bssid.clone();
let command_token = command.token.unwrap();
// Issue a "connect" command
let state = state.connect(command, &mut h.context);
expect_info_event(&mut h.info_stream, InfoEvent::AssociationStarted { att_id: 1});
expect_join_request(&mut h.mlme_stream, &bss_ssid);
// (mlme->sme) Send a JoinConf as a response
let join_conf = create_join_conf(fidl_mlme::JoinResultCodes::Success);
let state = state.on_mlme_event(join_conf, &mut h.context);
expect_auth_req(&mut h.mlme_stream, bssid);
// (mlme->sme) Send an AuthenticateConf as a response
let auth_conf = create_auth_conf(bssid.clone(),
fidl_mlme::AuthenticateResultCodes::Success);
let state = state.on_mlme_event(auth_conf, &mut h.context);
expect_assoc_req(&mut h.mlme_stream, bssid);
// (mlme->sme) Send an AssociateConf
let assoc_conf = create_assoc_conf(fidl_mlme::AssociateResultCodes::Success);
let state = state.on_mlme_event(assoc_conf, &mut h.context);
assert!(suppl_mock.is_supplicant_started());
expect_info_event(&mut h.info_stream, InfoEvent::AssociationSuccess { att_id: 1 });
expect_info_event(&mut h.info_stream, InfoEvent::RsnaStarted { att_id: 1 });
// (mlme->sme) Send an EapolInd, mock supplicant with key frame
let update = SecAssocUpdate::TxEapolKeyFrame(test_utils::eapol_key_frame());
let state = on_eapol_ind(state, &mut h, bssid, &suppl_mock, vec![update]);
expect_eapol_req(&mut h.mlme_stream, bssid);
// (mlme->sme) Send an EapolInd, mock supplicant with keys
let ptk = SecAssocUpdate::Key(Key::Ptk(test_utils::ptk()));
let gtk = SecAssocUpdate::Key(Key::Gtk(test_utils::gtk()));
let state = on_eapol_ind(state, &mut h, bssid, &suppl_mock, vec![ptk, gtk]);
expect_set_keys(&mut h.mlme_stream, bssid);
// (mlme->sme) Send an EapolInd, mock supplicant with completion status
let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
let _state = on_eapol_ind(state, &mut h, bssid, &suppl_mock, vec![update]);
expect_set_ctrl_port(&mut h.mlme_stream, bssid, fidl_mlme::ControlledPortState::Open);
expect_connect_result(&mut h.user_stream, command_token, ConnectResult::Success);
expect_info_event(&mut h.info_stream, InfoEvent::RsnaEstablished { att_id: 1 });
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished { result: ConnectResult::Success, failure: None });
}
#[test]
fn join_failure() {
let mut h = TestHelper::new();
// Start in a "Joining" state
let state = State::Joining::<FakeTokens> { cmd: connect_command_one() };
// (mlme->sme) Send an unsuccessful JoinConf
let join_conf = MlmeEvent::JoinConf {
resp: fidl_mlme::JoinConfirm {
result_code: fidl_mlme::JoinResultCodes::JoinFailureTimeout
}
};
let state = state.on_mlme_event(join_conf, &mut h.context);
assert_eq!(idle_state(), state);
// User should be notified that connection attempt failed
expect_connect_result(
&mut h.user_stream,
connect_command_one().token.unwrap(),
ConnectResult::Failed,
);
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished {
result: ConnectResult::Failed,
failure: Some(ConnectFailure::JoinFailure(
fidl_mlme::JoinResultCodes::JoinFailureTimeout,
)),
},
);
}
#[test]
fn authenticate_failure() {
let mut h = TestHelper::new();
// Start in an "Authenticating" state
let state = State::Authenticating::<FakeTokens> { cmd: connect_command_one() };
// (mlme->sme) Send an unsuccessful AuthenticateConf
let auth_conf = MlmeEvent::AuthenticateConf {
resp: fidl_mlme::AuthenticateConfirm {
peer_sta_address: connect_command_one().bss.bssid,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
result_code: fidl_mlme::AuthenticateResultCodes::Refused,
}
};
let state = state.on_mlme_event(auth_conf, &mut h.context);
assert_eq!(idle_state(), state);
// User should be notified that connection attempt failed
expect_connect_result(
&mut h.user_stream,
connect_command_one().token.unwrap(),
ConnectResult::Failed,
);
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished {
result: ConnectResult::Failed,
failure: Some(ConnectFailure::AuthenticationFailure(
fidl_mlme::AuthenticateResultCodes::Refused,
)),
},
);
}
#[test]
fn associate_failure() {
let mut h = TestHelper::new();
// Start in an "Associating" state
let state = State::Associating::<FakeTokens> { cmd: connect_command_one() };
// (mlme->sme) Send an unsuccessful AssociateConf
let assoc_conf = create_assoc_conf(
fidl_mlme::AssociateResultCodes::RefusedReasonUnspecified);
let state = state.on_mlme_event(assoc_conf, &mut h.context);
assert_eq!(idle_state(), state);
// User should be notified that connection attempt failed
expect_connect_result(
&mut h.user_stream,
connect_command_one().token.unwrap(),
ConnectResult::Failed,
);
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished {
result: ConnectResult::Failed,
failure: Some(ConnectFailure::AssociationFailure(
fidl_mlme::AssociateResultCodes::RefusedReasonUnspecified,
)),
},
);
}
#[test]
fn connect_while_joining() {
let mut h = TestHelper::new();
let state = joining_state(connect_command_one());
let state = state.connect(connect_command_two(), &mut h.context);
expect_connect_result(&mut h.user_stream, connect_command_one().token.unwrap(),
ConnectResult::Canceled);
expect_join_request(&mut h.mlme_stream, &connect_command_two().bss.ssid);
assert_eq!(joining_state(connect_command_two()), state);
}
#[test]
fn connect_while_authenticating() {
let mut h = TestHelper::new();
let state = authenticating_state(connect_command_one());
let state = state.connect(connect_command_two(), &mut h.context);
expect_connect_result(&mut h.user_stream, connect_command_one().token.unwrap(),
ConnectResult::Canceled);
expect_join_request(&mut h.mlme_stream, &connect_command_two().bss.ssid);
assert_eq!(joining_state(connect_command_two()), state);
}
#[test]
fn connect_while_associating() {
let mut h = TestHelper::new();
let state = associating_state(connect_command_one());
let state = state.connect(connect_command_two(), &mut h.context);
let state = exchange_deauth(state, &mut h);
expect_connect_result(&mut h.user_stream, connect_command_one().token.unwrap(),
ConnectResult::Canceled);
expect_join_request(&mut h.mlme_stream, &connect_command_two().bss.ssid);
assert_eq!(joining_state(connect_command_two()), state);
}
#[test]
fn supplicant_fails_to_start_while_associating() {
let mut h = TestHelper::new();
let (supplicant, suppl_mock) = mock_supplicant();
let command = connect_command_rsna(supplicant);
let bssid = command.bss.bssid.clone();
let token = command.token.unwrap();
let state = associating_state(command);
suppl_mock.set_start_failure(format_err!("failed to start supplicant"));
// (mlme->sme) Send an AssociateConf
let assoc_conf = create_assoc_conf(fidl_mlme::AssociateResultCodes::Success);
let _state = state.on_mlme_event(assoc_conf, &mut h.context);
expect_deauth_req(&mut h.mlme_stream, bssid, fidl_mlme::ReasonCode::StaLeaving);
expect_connect_result(&mut h.user_stream, token, ConnectResult::Failed);
expect_info_event(&mut h.info_stream, InfoEvent::AssociationSuccess { att_id: 0 });
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished {
result: ConnectResult::Failed,
failure: Some(ConnectFailure::AssociationFailure(
fidl_mlme::AssociateResultCodes::RefusedReasonUnspecified,
)),
},
);
}
#[test]
fn bad_eapol_frame_while_establishing_rsna() {
let mut h = TestHelper::new();
let (supplicant, suppl_mock) = mock_supplicant();
let command = connect_command_rsna(supplicant);
let bssid = command.bss.bssid.clone();
let state = establishing_rsna_state(command);
// doesn't matter what we mock here
let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
suppl_mock.set_on_eapol_frame_results(vec![update]);
// (mlme->sme) Send an EapolInd with bad eapol data
let eapol_ind = create_eapol_ind(bssid.clone(), vec![1, 2, 3, 4]);
let state = state.on_mlme_event(eapol_ind, &mut h.context);
match state {
State::Associated { link_state, .. } => match link_state {
LinkState::EstablishingRsna { .. } => (), // expected path
_ => panic!("expect link state to still be establishing RSNA"),
},
_ => panic!("expect state to still be associated"),
}
expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
expect_stream_empty(&mut h.user_stream, "unexpected event in user stream");
expect_stream_empty(&mut h.info_stream, "unexpected event in info stream");
}
#[test]
fn supplicant_fails_to_process_eapol_while_establishing_rsna() {
let mut h = TestHelper::new();
let (supplicant, suppl_mock) = mock_supplicant();
let command = connect_command_rsna(supplicant);
let bssid = command.bss.bssid.clone();
let state = establishing_rsna_state(command);
suppl_mock.set_on_eapol_frame_failure(format_err!("supplicant::on_eapol_frame fails"));
// (mlme->sme) Send an EapolInd
let eapol_ind = create_eapol_ind(bssid.clone(), test_utils::eapol_key_frame_bytes());
let state = state.on_mlme_event(eapol_ind, &mut h.context);
match state {
State::Associated { link_state, .. } => match link_state {
LinkState::EstablishingRsna { .. } => (), // expected path
_ => panic!("expect link state to still be establishing RSNA"),
},
_ => panic!("expect state to still be associated"),
}
expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
expect_stream_empty(&mut h.user_stream, "unexpected event in user stream");
expect_stream_empty(&mut h.info_stream, "unexpected event in info stream");
}
#[test]
fn wrong_password_while_establishing_rsna() {
let mut h = TestHelper::new();
let (supplicant, suppl_mock) = mock_supplicant();
let command = connect_command_rsna(supplicant);
let bssid = command.bss.bssid.clone();
let token = command.token.unwrap();
let state = establishing_rsna_state(command);
// (mlme->sme) Send an EapolInd, mock supplicant with wrong password status
let update = SecAssocUpdate::Status(SecAssocStatus::WrongPassword);
let _state = on_eapol_ind(state, &mut h, bssid, &suppl_mock, vec![update]);
expect_deauth_req(&mut h.mlme_stream, bssid, fidl_mlme::ReasonCode::StaLeaving);
expect_connect_result(&mut h.user_stream, token, ConnectResult::BadCredentials);
expect_info_event(
&mut h.info_stream,
InfoEvent::ConnectFinished {
result: ConnectResult::BadCredentials,
failure: None,
},
);
}
#[test]
fn overall_timeout_while_establishing_rsna() {
let mut h = TestHelper::new();
let (supplicant, _suppl_mock) = mock_supplicant();
let command = connect_command_rsna(supplicant);
let bssid = command.bss.bssid.clone();
let token = command.token.unwrap();
// Start in an "Associating" state
let state = State::Associating::<FakeTokens> { cmd: command };
let assoc_conf = create_assoc_conf(fidl_mlme::AssociateResultCodes::Success);
let state = state.on_mlme_event(assoc_conf, &mut h.context);
let (_, timed_event) = h.time_stream.try_next().unwrap().expect("expect timed event");
match timed_event.event {
Event::EstablishingRsnaTimeout => (), // expected path
_ => panic!("expect EstablishingRsnaTimeout timeout event"),
}
expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
let _state = state.handle_timeout(timed_event.id, timed_event.event, &mut h.context);
expect_deauth_req(&mut h.mlme_stream, bssid, fidl_mlme::ReasonCode::StaLeaving);
expect_connect_result(&mut h.user_stream, token, ConnectResult::Failed);
}
#[test]
fn key_frame_exchange_timeout_while_establishing_rsna() {
let mut h = TestHelper::new();
let (supplicant, suppl_mock) = mock_supplicant();
let command = connect_command_rsna(supplicant);
let bssid = command.bss.bssid.clone();
let token = command.token.unwrap();
let state = establishing_rsna_state(command);
// (mlme->sme) Send an EapolInd, mock supplication with key frame
let update = SecAssocUpdate::TxEapolKeyFrame(test_utils::eapol_key_frame());
let mut state = on_eapol_ind(state, &mut h, bssid, &suppl_mock, vec![update]);
for i in 1..=3 {
println!("send eapol attempt: {}", i);
expect_eapol_req(&mut h.mlme_stream, bssid);
expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
let (_, timed_event) = h.time_stream.try_next().unwrap().expect("expect timed event");
match timed_event.event {
Event::KeyFrameExchangeTimeout { attempt, .. } => assert_eq!(attempt, i),
_ => panic!("expect EstablishingRsnaTimeout timeout event"),
}
state = state.handle_timeout(timed_event.id, timed_event.event, &mut h.context);
}
expect_deauth_req(&mut h.mlme_stream, bssid, fidl_mlme::ReasonCode::StaLeaving);
expect_connect_result(&mut h.user_stream, token, ConnectResult::Failed);
}
#[test]
fn connect_while_link_up() {
let mut h = TestHelper::new();
let state = link_up_state(connect_command_one().bss);
let state = state.connect(connect_command_two(), &mut h.context);
let state = exchange_deauth(state, &mut h);
expect_join_request(&mut h.mlme_stream, &connect_command_two().bss.ssid);
assert_eq!(joining_state(connect_command_two()), state);
}
#[test]
fn disconnect_while_idle() {
let mut h = TestHelper::new();
let new_state = idle_state().disconnect(&h.context);
assert_eq!(idle_state(), new_state);
// Expect no messages to the MLME or the user
assert!(h.mlme_stream.try_next().is_err());
assert!(h.user_stream.try_next().is_err());
}
#[test]
fn disconnect_while_joining() {
let mut h = TestHelper::new();
let state = joining_state(connect_command_one());
let state = state.disconnect(&h.context);
expect_connect_result(&mut h.user_stream, connect_command_one().token.unwrap(),
ConnectResult::Canceled);
assert_eq!(idle_state(), state);
}
#[test]
fn disconnect_while_authenticating() {
let mut h = TestHelper::new();
let state = authenticating_state(connect_command_one());
let state = state.disconnect(&h.context);
expect_connect_result(&mut h.user_stream, connect_command_one().token.unwrap(),
ConnectResult::Canceled);
assert_eq!(idle_state(), state);
}
#[test]
fn disconnect_while_associating() {
let mut h = TestHelper::new();
let state = associating_state(connect_command_one());
let state = state.disconnect(&h.context);
let state = exchange_deauth(state, &mut h);
expect_connect_result(&mut h.user_stream, connect_command_one().token.unwrap(),
ConnectResult::Canceled);
assert_eq!(idle_state(), state);
}
#[test]
fn disconnect_while_link_up() {
let mut h = TestHelper::new();
let state = link_up_state(connect_command_one().bss);
let state = state.disconnect(&h.context);
let state = exchange_deauth(state, &mut h);
assert_eq!(idle_state(), state);
}
#[test]
fn increment_att_id_on_connect() {
let mut h = TestHelper::new();
let state = idle_state();
assert_eq!(h.context.att_id, 0);
let state = state.connect(connect_command_one(), &mut h.context);
assert_eq!(h.context.att_id, 1);
let state = state.disconnect(&h.context);
assert_eq!(h.context.att_id, 1);
let state = state.connect(connect_command_two(), &mut h.context);
assert_eq!(h.context.att_id, 2);
let _state = state.connect(connect_command_one(), &mut h.context);
assert_eq!(h.context.att_id, 3);
}
#[test]
fn increment_att_id_on_disassociate_ind() {
let mut h = TestHelper::new();
let state = link_up_state(connect_command_no_token().bss);
assert_eq!(h.context.att_id, 0);
let disassociate_ind = MlmeEvent::DisassociateInd {
ind: fidl_mlme::DisassociateIndication {
peer_sta_address: [0, 0, 0, 0, 0, 0],
reason_code: 0,
}
};
let state = state.on_mlme_event(disassociate_ind, &mut h.context);
assert_eq!(associating_state(connect_command_no_token()), state);
assert_eq!(h.context.att_id, 1);
}
struct TestHelper {
mlme_stream: MlmeStream,
user_stream: UserStream<FakeTokens>,
info_stream: InfoStream,
time_stream: TimeStream,
context: Context<FakeTokens>,
}
impl TestHelper {
fn new() -> Self {
let (mlme_sink, mlme_stream) = mpsc::unbounded();
let (user_sink, user_stream) = mpsc::unbounded();
let (info_sink, info_stream) = mpsc::unbounded();
let (timer, time_stream) = timer::create_timer();
let context = Context {
device_info: Arc::new(fake_device_info()),
mlme_sink: MlmeSink::new(mlme_sink),
user_sink: UserSink::new(user_sink),
info_sink: InfoSink::new(info_sink),
timer,
att_id: 0,
};
TestHelper { mlme_stream, user_stream, info_stream, time_stream, context }
}
}
fn on_eapol_ind(state: State<FakeTokens>, helper: &mut TestHelper, bssid: [u8; 6],
suppl_mock: &MockSupplicantController, update_sink: UpdateSink)
-> State<FakeTokens> {
suppl_mock.set_on_eapol_frame_results(update_sink);
// (mlme->sme) Send an EapolInd
let eapol_ind = create_eapol_ind(bssid.clone(), test_utils::eapol_key_frame_bytes());
state.on_mlme_event(eapol_ind, &mut helper.context)
}
fn create_join_conf(result_code: fidl_mlme::JoinResultCodes) -> MlmeEvent {
MlmeEvent::JoinConf {
resp: fidl_mlme::JoinConfirm {
result_code,
}
}
}
fn create_auth_conf(bssid: [u8; 6], result_code: fidl_mlme::AuthenticateResultCodes)
-> MlmeEvent {
MlmeEvent::AuthenticateConf {
resp: fidl_mlme::AuthenticateConfirm {
peer_sta_address: bssid,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
result_code,
}
}
}
fn create_assoc_conf(result_code: fidl_mlme::AssociateResultCodes) -> MlmeEvent {
MlmeEvent::AssociateConf {
resp: fidl_mlme::AssociateConfirm {
result_code,
association_id: 55,
}
}
}
fn create_eapol_ind(bssid: [u8; 6], data: Vec<u8>) -> MlmeEvent {
MlmeEvent::EapolInd {
ind: fidl_mlme::EapolIndication {
src_addr: bssid,
dst_addr: fake_device_info().addr,
data,
},
}
}
fn exchange_deauth(state: State<FakeTokens>, h: &mut TestHelper) -> State<FakeTokens> {
// (sme->mlme) Expect a DeauthenticateRequest
match h.mlme_stream.try_next().unwrap() {
Some(MlmeRequest::Deauthenticate(req)) => {
assert_eq!(connect_command_one().bss.bssid, req.peer_sta_address);
},
other => panic!("expected a Deauthenticate request, got {:?}", other),
}
// (mlme->sme) Send a DeauthenticateConf as a response
let deauth_conf = MlmeEvent::DeauthenticateConf {
resp: fidl_mlme::DeauthenticateConfirm {
peer_sta_address: connect_command_one().bss.bssid,
}
};
state.on_mlme_event(deauth_conf, &mut h.context)
}
fn expect_join_request(mlme_stream: &mut MlmeStream, ssid: &[u8]) {
// (sme->mlme) Expect a JoinRequest
match mlme_stream.try_next().unwrap() {
Some(MlmeRequest::Join(req)) => assert_eq!(ssid, &req.selected_bss.ssid[..]),
_ => panic!("expect set keys req to MLME"),
}
}
fn expect_set_ctrl_port(mlme_stream: &mut MlmeStream, bssid: [u8; 6],
state: fidl_mlme::ControlledPortState) {
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetCtrlPort(req) => {
assert_eq!(req.peer_sta_address, bssid);
assert_eq!(req.state, state);
},
other => panic!("expected a Join request, got {:?}", other),
}
}
fn expect_auth_req(mlme_stream: &mut MlmeStream, bssid: [u8; 6]) {
// (sme->mlme) Expect an AuthenticateRequest
match mlme_stream.try_next().unwrap() {
Some(MlmeRequest::Authenticate(req)) => assert_eq!(bssid, req.peer_sta_address),
other => panic!("expected an Authenticate request, got {:?}", other)
}
}
fn expect_deauth_req(mlme_stream: &mut MlmeStream, bssid: [u8; 6],
reason_code: fidl_mlme::ReasonCode) {
// (sme->mlme) Expect a DeauthenticateRequest
match mlme_stream.try_next().unwrap() {
Some(MlmeRequest::Deauthenticate(req)) => {
assert_eq!(bssid, req.peer_sta_address);
assert_eq!(reason_code, req.reason_code);
},
other => panic!("expected an Deauthenticate request, got {:?}", other)
}
}
fn expect_assoc_req(mlme_stream: &mut MlmeStream, bssid: [u8; 6]) {
match mlme_stream.try_next().unwrap() {
Some(MlmeRequest::Associate(req)) => assert_eq!(bssid, req.peer_sta_address),
other => panic!("expected an Associate request, got {:?}", other),
}
}
fn expect_eapol_req(mlme_stream: &mut MlmeStream, bssid: [u8; 6]) {
match mlme_stream.try_next().unwrap() {
Some(MlmeRequest::Eapol(req)) => {
assert_eq!(req.src_addr, fake_device_info().addr);
assert_eq!(req.dst_addr, bssid);
assert_eq!(req.data, test_utils::eapol_key_frame_bytes());
}
other => panic!("expected an Eapol request, got {:?}", other),
}
}
fn expect_set_keys(mlme_stream: &mut MlmeStream, bssid: [u8; 6]) {
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetKeys(set_keys_req) => {
assert_eq!(set_keys_req.keylist.len(), 1);
let k = set_keys_req.keylist.get(0).expect("expect key descriptor");
assert_eq!(k.key, vec![0xCCu8; test_utils::cipher().tk_bytes().unwrap()]);
assert_eq!(k.key_id, 0);
assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
assert_eq!(k.address, bssid);
assert_eq!(k.rsc, [0u8; 8]);
assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
assert_eq!(k.cipher_suite_type, 4);
},
_ => panic!("expect set keys req to MLME"),
}
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetKeys(set_keys_req) => {
assert_eq!(set_keys_req.keylist.len(), 1);
let k = set_keys_req.keylist.get(0).expect("expect key descriptor");
assert_eq!(k.key, test_utils::gtk_bytes());
assert_eq!(k.key_id, 2);
assert_eq!(k.key_type, fidl_mlme::KeyType::Group);
assert_eq!(k.address, [0xFFu8; 6]);
assert_eq!(k.rsc, [0u8; 8]);
assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
assert_eq!(k.cipher_suite_type, 4);
},
_ => panic!("expect set keys req to MLME"),
}
}
fn expect_connect_result(user_stream: &mut UserStream<FakeTokens>,
expected_token: u32,
expected_result: ConnectResult) {
match user_stream.try_next().unwrap() {
Some(UserEvent::ConnectFinished { token, result }) => {
assert_eq!(expected_token, token);
assert_eq!(expected_result, result);
},
other => panic!("expected a ConnectFinished event, got {:?}", other)
}
}
fn expect_stream_empty<T>(stream: &mut mpsc::UnboundedReceiver<T>, error_msg: &str) {
match stream.try_next() {
Err(e) => assert_eq!(e.description(), "receiver channel is empty"),
_ => panic!("{}", error_msg),
}
}
fn connect_command_one() -> ConnectCommand<u32> {
ConnectCommand {
bss: Box::new(unprotected_bss(b"foo".to_vec(), [7, 7, 7, 7, 7, 7])),
token: Some(123_u32),
rsna: None,
params: ConnectPhyParams { phy: None, cbw: None, },
}
}
fn connect_command_two() -> ConnectCommand<u32> {
ConnectCommand {
bss: Box::new(unprotected_bss(b"bar".to_vec(), [8, 8, 8, 8, 8, 8])),
token: Some(456_u32),
rsna: None,
params: ConnectPhyParams { phy: None, cbw: None, },
}
}
fn connect_command_no_token() -> ConnectCommand<u32> {
ConnectCommand {
bss: Box::new(unprotected_bss(b"bar".to_vec(), [8, 8, 8, 8, 8, 8])),
token: None,
rsna: None,
params: ConnectPhyParams { phy: None, cbw: None, },
}
}
fn connect_command_rsna(supplicant: MockSupplicant) -> ConnectCommand<u32> {
let bss = protected_bss(b"foo".to_vec(), [7, 7, 7, 7, 7, 7]);
let rsne = test_utils::wpa2_psk_ccmp_rsne_with_caps(RsnCapabilities(0));
ConnectCommand {
bss: Box::new(bss),
token: Some(123_u32),
rsna: Some(Rsna {
negotiated_rsne: NegotiatedRsne::from_rsne(&rsne).expect("invalid NegotiatedRsne"),
supplicant: Box::new(supplicant),
}),
params: ConnectPhyParams { phy: None, cbw: None, },
}
}
fn idle_state() -> State<FakeTokens> {
State::Idle
}
fn joining_state(cmd: ConnectCommand<u32>) -> State<FakeTokens> {
State::Joining { cmd }
}
fn authenticating_state(cmd: ConnectCommand<u32>) -> State<FakeTokens> {
State::Authenticating { cmd }
}
fn associating_state(cmd: ConnectCommand<u32>) -> State<FakeTokens> {
State::Associating { cmd }
}
fn establishing_rsna_state(cmd: ConnectCommand<u32>) -> State<FakeTokens> {
let rsna = cmd.rsna.expect("expect rsna for establishing_rsna_state");
State::Associated {
bss: cmd.bss,
last_rssi: None,
link_state: LinkState::EstablishingRsna {
token: cmd.token,
rsna,
rsna_timeout: None,
resp_timeout: None,
},
params: ConnectPhyParams { phy: None, cbw: None },
}
}
fn link_up_state(bss: Box<fidl_mlme::BssDescription>) -> State<FakeTokens> {
State::Associated {
bss,
last_rssi: None,
link_state: LinkState::LinkUp(None),
params: ConnectPhyParams { phy: None, cbw: None, },
}
}
fn protected_bss(ssid: Ssid, bssid: [u8; 6]) -> fidl_mlme::BssDescription {
fidl_mlme::BssDescription {
bssid,
.. fake_protected_bss_description(ssid)
}
}
fn unprotected_bss(ssid: Ssid, bssid: [u8; 6]) -> fidl_mlme::BssDescription {
fidl_mlme::BssDescription {
bssid,
.. fake_unprotected_bss_description(ssid)
}
}
fn fake_device_info() -> DeviceInfo {
DeviceInfo {
addr: [ 0, 1, 2, 3, 4, 5 ],
bands: vec![],
}
}
}