blob: 5d3b20048c4b8847c5c88c2c51e0466c3d01ce58 [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.
mod aid;
mod authenticator;
mod event;
mod remote_client;
mod rsn;
#[cfg(test)]
mod test_utils;
use crate::ap::{
aid::AssociationId,
authenticator::Authenticator,
event::{Event, SmeEvent},
rsn::{create_wpa2_psk_rsne, is_valid_rsne_subset},
};
use crate::responder::Responder;
use crate::sink::MlmeSink;
use crate::timer::{self, EventId, TimedEvent, Timer};
use crate::{DeviceInfo, MacAddr, MlmeRequest, Ssid};
use failure::{bail, ensure};
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_mlme::{self as fidl_mlme, MlmeEvent};
use futures::channel::{mpsc, oneshot};
use log::{debug, error, info, warn};
use std::boxed::Box;
use std::sync::{Arc, Mutex};
use wlan_common::channel::{Cbw, Channel};
use wlan_rsn::{self, gtk::GtkProvider, nonce::NonceReader, psk, rsne::Rsne, NegotiatedRsne};
const DEFAULT_BEACON_PERIOD: u16 = 100;
const DEFAULT_DTIM_PERIOD: u8 = 1;
#[derive(Clone, Debug, PartialEq)]
pub struct Config {
pub ssid: Ssid,
pub password: Vec<u8>,
pub channel: u8,
}
pub type TimeStream = timer::TimeStream<Event>;
enum State {
Idle {
ctx: Context,
},
Starting {
ctx: Context,
ssid: Ssid,
rsn_cfg: Option<RsnCfg>,
responder: Responder<StartResult>,
start_timeout: EventId,
},
Started {
bss: InfraBss,
},
}
#[derive(Clone)]
struct RsnCfg {
psk: psk::Psk,
rsne: Rsne,
}
struct InfraBss {
ssid: Ssid,
rsn_cfg: Option<RsnCfg>,
client_map: remote_client::Map,
ctx: Context,
}
pub struct Context {
device_info: DeviceInfo,
mlme_sink: MlmeSink,
timer: Timer<Event>,
}
pub struct ApSme {
state: Option<State>,
}
#[derive(Debug, PartialEq)]
pub enum StartResult {
Success,
AlreadyStarted,
InternalError,
Canceled,
TimedOut,
PreviousStartInProgress,
InvalidArguments,
DfsUnsupported,
}
impl ApSme {
pub fn new(device_info: DeviceInfo) -> (Self, crate::MlmeStream, TimeStream) {
let (mlme_sink, mlme_stream) = mpsc::unbounded();
let (timer, time_stream) = timer::create_timer();
let sme = ApSme {
state: Some(State::Idle {
ctx: Context { device_info, mlme_sink: MlmeSink::new(mlme_sink), timer },
}),
};
(sme, mlme_stream, time_stream)
}
pub fn on_start_command(&mut self, config: Config) -> oneshot::Receiver<StartResult> {
let (responder, receiver) = Responder::new();
self.state = self.state.take().map(|state| match state {
State::Idle { mut ctx } => {
if let Err(result) = validate_config(&config) {
responder.respond(result);
return State::Idle { ctx };
}
let rsn_cfg_result = create_rsn_cfg(&config.ssid[..], &config.password[..]);
match rsn_cfg_result {
Err(e) => {
error!("error configuring RSN: {}", e);
responder.respond(StartResult::InternalError);
State::Idle { ctx }
}
Ok(rsn_cfg) => {
let req = create_start_request(&config, rsn_cfg.as_ref());
ctx.mlme_sink.send(MlmeRequest::Start(req));
let event = Event::Sme { event: SmeEvent::StartTimeout };
let start_timeout = ctx.timer.schedule(event);
State::Starting {
ctx,
ssid: config.ssid,
rsn_cfg,
responder,
start_timeout,
}
}
}
}
s @ State::Starting { .. } => {
responder.respond(StartResult::PreviousStartInProgress);
s
}
s @ State::Started { .. } => {
responder.respond(StartResult::AlreadyStarted);
s
}
});
receiver
}
pub fn on_stop_command(&mut self) -> oneshot::Receiver<()> {
let (stop_responder, receiver) = Responder::new();
self.state = self.state.take().map(|state| match state {
s @ State::Idle { .. } => {
stop_responder.respond(());
s
}
State::Starting { ctx, responder: start_responder, .. } => {
start_responder.respond(StartResult::Canceled);
stop_responder.respond(());
State::Idle { ctx }
}
State::Started { bss } => {
let req = fidl_mlme::StopRequest { ssid: bss.ssid.clone() };
bss.ctx.mlme_sink.send(MlmeRequest::Stop(req));
// Currently, MLME doesn't send any response back. We simply assume
// that the stop request succeeded immediately
stop_responder.respond(());
State::Idle { ctx: bss.ctx }
}
});
receiver
}
}
impl super::Station for ApSme {
type Event = Event;
fn on_mlme_event(&mut self, event: MlmeEvent) {
debug!("received MLME event: {:?}", event);
self.state = self.state.take().map(|mut state| match state {
State::Idle { .. } => {
warn!("received MlmeEvent while ApSme is idle {:?}", event);
state
}
State::Starting { ctx, ssid, rsn_cfg, responder, start_timeout } => match event {
MlmeEvent::StartConf { resp } => {
handle_start_conf(resp, ctx, ssid, rsn_cfg, responder)
}
_ => {
warn!("received MlmeEvent while ApSme is starting {:?}", event);
State::Starting { ctx, ssid, rsn_cfg, responder, start_timeout }
}
},
State::Started { ref mut bss } => {
match event {
MlmeEvent::AuthenticateInd { ind } => bss.handle_auth_ind(ind),
MlmeEvent::DeauthenticateInd { ind } => {
bss.handle_deauth(&ind.peer_sta_address)
}
MlmeEvent::DeauthenticateConf { resp } => {
bss.handle_deauth(&resp.peer_sta_address)
}
MlmeEvent::AssociateInd { ind } => bss.handle_assoc_ind(ind),
MlmeEvent::DisassociateInd { ind } => bss.handle_disassoc_ind(ind),
MlmeEvent::EapolInd { ind } => {
let _ = bss.handle_eapol_ind(ind).map_err(|e| warn!("{}", e));
}
MlmeEvent::EapolConf { resp } => {
if resp.result_code != fidl_mlme::EapolResultCodes::Success {
// TODO(NET-1634) - Handle unsuccessful EAPoL confirmation. It doesn't
// include client address, though. Maybe we can just
// ignore these messages and just set a handshake
// timeout instead
info!("Received unsuccessful EapolConf");
}
}
_ => warn!("unsupported MlmeEvent type {:?}; ignoring", event),
}
state
}
});
}
fn on_timeout(&mut self, timed_event: TimedEvent<Event>) {
self.state = self.state.take().map(|mut state| match state {
State::Idle { .. } => state,
State::Starting { start_timeout, ctx, responder, ssid, rsn_cfg } => {
match timed_event.event {
Event::Sme { event } => match event {
SmeEvent::StartTimeout if start_timeout == timed_event.id => {
warn!("Timed out waiting for MLME to start");
responder.respond(StartResult::TimedOut);
State::Idle { ctx }
}
_ => State::Starting { start_timeout, ctx, responder, ssid, rsn_cfg },
},
_ => State::Starting { start_timeout, ctx, responder, ssid, rsn_cfg },
}
}
State::Started { ref mut bss } => {
bss.handle_timeout(timed_event);
state
}
});
}
}
fn validate_config(config: &Config) -> Result<(), StartResult> {
let c = Channel::new(config.channel.clone(), Cbw::Cbw20);
if !c.is_valid() {
Err(StartResult::InvalidArguments)
} else if c.is_dfs() {
Err(StartResult::DfsUnsupported)
} else {
Ok(())
}
}
fn handle_start_conf(
conf: fidl_mlme::StartConfirm,
ctx: Context,
ssid: Ssid,
rsn_cfg: Option<RsnCfg>,
responder: Responder<StartResult>,
) -> State {
match conf.result_code {
fidl_mlme::StartResultCodes::Success => {
responder.respond(StartResult::Success);
State::Started { bss: InfraBss { ssid, rsn_cfg, client_map: Default::default(), ctx } }
}
result_code => {
error!("failed to start BSS: {:?}", result_code);
responder.respond(StartResult::InternalError);
State::Idle { ctx }
}
}
}
impl InfraBss {
fn handle_auth_ind(&mut self, ind: fidl_mlme::AuthenticateIndication) {
if self.client_map.remove_client(&ind.peer_sta_address).is_some() {
warn!(
"client {:?} authenticates while still associated; removed client from map",
ind.peer_sta_address
);
}
let result_code = if ind.auth_type == fidl_mlme::AuthenticationTypes::OpenSystem {
fidl_mlme::AuthenticateResultCodes::Success
} else {
warn!("unsupported authentication type {:?}", ind.auth_type);
fidl_mlme::AuthenticateResultCodes::Refused
};
let resp =
fidl_mlme::AuthenticateResponse { peer_sta_address: ind.peer_sta_address, result_code };
self.ctx.mlme_sink.send(MlmeRequest::AuthResponse(resp));
}
fn handle_deauth(&mut self, client_addr: &MacAddr) {
let _ = self.client_map.remove_client(client_addr);
}
fn handle_assoc_ind(&mut self, ind: fidl_mlme::AssociateIndication) {
if self.client_map.remove_client(&ind.peer_sta_address).is_some() {
warn!(
"client {:?} associates while still associated; removed client from map",
ind.peer_sta_address
);
}
let result = match (ind.rsn.as_ref(), self.rsn_cfg.clone()) {
(Some(s_rsne_bytes), Some(a_rsn)) => {
self.handle_rsn_assoc_ind(s_rsne_bytes, a_rsn, &ind.peer_sta_address)
}
(None, None) => self.add_client(ind.peer_sta_address.clone(), None),
_ => {
warn!("unexpected RSN element from client: {:?}", ind.rsn);
Err(fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch)
}
};
let (aid, result_code) = match result {
Ok(aid) => (aid, fidl_mlme::AssociateResultCodes::Success),
Err(result_code) => (0, result_code),
};
let resp = fidl_mlme::AssociateResponse {
peer_sta_address: ind.peer_sta_address.clone(),
result_code,
association_id: aid,
};
self.ctx.mlme_sink.send(MlmeRequest::AssocResponse(resp));
if result_code == fidl_mlme::AssociateResultCodes::Success && self.rsn_cfg.is_some() {
match self.client_map.get_mut_client(&ind.peer_sta_address) {
Some(client) => client.initiate_key_exchange(&mut self.ctx, 1),
None => error!(
"cannot initiate key exchange for unknown client: {:02X?}",
ind.peer_sta_address
),
}
}
}
fn handle_disassoc_ind(&mut self, ind: fidl_mlme::DisassociateIndication) {
let _ = self.client_map.remove_client(&ind.peer_sta_address);
}
fn handle_rsn_assoc_ind(
&mut self,
s_rsne_bytes: &Vec<u8>,
a_rsn: RsnCfg,
client_addr: &MacAddr,
) -> Result<AssociationId, fidl_mlme::AssociateResultCodes> {
let s_rsne = wlan_rsn::rsne::from_bytes(s_rsne_bytes).to_full_result().map_err(|e| {
warn!("failed to deserialize RSNE: {:?}", e);
fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch
})?;
validate_s_rsne(&s_rsne, &a_rsn.rsne).map_err(|_| {
warn!("incompatible client RSNE");
fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch
})?;
// Note: There should be one Reader per device, not per SME.
// Follow-up with improving on this.
let nonce_rdr = NonceReader::new(&self.ctx.device_info.addr[..]).map_err(|e| {
warn!("failed to create NonceReader: {}", e);
fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch
})?;
let gtk_provider = get_gtk_provider(&s_rsne).map_err(|e| {
warn!("failed to create GtkProvider: {}", e);
fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch
})?;
let authenticator = wlan_rsn::Authenticator::new_wpa2psk_ccmp128(
nonce_rdr,
gtk_provider,
a_rsn.psk.clone(),
client_addr.clone(),
s_rsne,
self.ctx.device_info.addr,
a_rsn.rsne,
)
.map_err(|e| {
warn!("failed to create authenticator: {}", e);
fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch
})?;
self.add_client(client_addr.clone(), Some(Box::new(authenticator)))
}
fn add_client(
&mut self,
addr: MacAddr,
auth: Option<Box<Authenticator>>,
) -> Result<AssociationId, fidl_mlme::AssociateResultCodes> {
self.client_map.add_client(addr, auth).map_err(|e| {
warn!("unable to add user to client map: {}", e);
fidl_mlme::AssociateResultCodes::RefusedReasonUnspecified
})
}
fn handle_eapol_ind(&mut self, ind: fidl_mlme::EapolIndication) -> Result<(), failure::Error> {
let client_addr = &ind.src_addr;
match self.client_map.get_mut_client(client_addr) {
Some(client) => client.handle_eapol_ind(ind, &mut self.ctx),
None => bail!("client {:02X?} not found; ignoring EapolInd msg", client_addr),
}
}
fn handle_timeout(&mut self, timed_event: TimedEvent<Event>) {
match timed_event.event {
Event::Sme { .. } => (),
Event::Client { addr, event } => {
if let Some(client) = self.client_map.get_mut_client(&addr) {
client.handle_timeout(timed_event.id, event, &mut self.ctx);
}
}
}
}
}
fn validate_s_rsne(s_rsne: &Rsne, a_rsne: &Rsne) -> Result<(), failure::Error> {
ensure!(is_valid_rsne_subset(s_rsne, a_rsne)?, "incompatible client RSNE");
Ok(())
}
fn get_gtk_provider(s_rsne: &Rsne) -> Result<Arc<Mutex<GtkProvider>>, failure::Error> {
let negotiated_rsne = NegotiatedRsne::from_rsne(&s_rsne)?;
let gtk_provider = GtkProvider::new(negotiated_rsne.group_data)?;
Ok(Arc::new(Mutex::new(gtk_provider)))
}
fn create_rsn_cfg(ssid: &[u8], password: &[u8]) -> Result<Option<RsnCfg>, failure::Error> {
if password.is_empty() {
Ok(None)
} else {
let psk = psk::compute(password, ssid)?;
Ok(Some(RsnCfg { psk, rsne: create_wpa2_psk_rsne() }))
}
}
fn create_start_request(config: &Config, ap_rsn: Option<&RsnCfg>) -> fidl_mlme::StartRequest {
let rsne_bytes = ap_rsn.as_ref().map(|RsnCfg { rsne, .. }| {
let mut buf = Vec::with_capacity(rsne.len());
rsne.as_bytes(&mut buf);
buf
});
fidl_mlme::StartRequest {
ssid: config.ssid.clone(),
bss_type: fidl_mlme::BssTypes::Infrastructure,
beacon_period: DEFAULT_BEACON_PERIOD,
dtim_period: DEFAULT_DTIM_PERIOD,
channel: config.channel,
country: fidl_mlme::Country {
// TODO(WLAN-870): Get config from wlancfg
alpha2: ['U' as u8, 'S' as u8],
suffix: fidl_mlme::COUNTRY_ENVIRON_ALL,
},
rsne: rsne_bytes,
mesh_id: vec![],
phy: fidl_common::Phy::Ht, // TODO(WLAN-908, WLAN-909): Use dynamic value
cbw: fidl_common::Cbw::Cbw20,
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use std::error::Error;
use crate::{MlmeStream, Station};
const AP_ADDR: [u8; 6] = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
const CLIENT_ADDR: [u8; 6] = [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67];
const CLIENT_ADDR2: [u8; 6] = [0x22, 0x22, 0x22, 0x22, 0x22, 0x22];
const SSID: &'static [u8] = &[0x46, 0x55, 0x43, 0x48, 0x53, 0x49, 0x41];
const RSNE: &'static [u8] = &[
0x30, // element id
0x2A, // length
0x01, 0x00, // version
0x00, 0x0f, 0xac, 0x04, // group data cipher suite -- CCMP-128
0x01, 0x00, // pairwise cipher suite count
0x00, 0x0f, 0xac, 0x04, // pairwise cipher suite list -- CCMP-128
0x01, 0x00, // akm suite count
0x00, 0x0f, 0xac, 0x02, // akm suite list -- PSK
0xa8, 0x04, // rsn capabilities
0x01, 0x00, // pmk id count
// pmk id list
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x11, 0x00, 0x0f, 0xac, 0x04, // group management cipher suite -- CCMP-128
];
fn unprotected_config() -> Config {
Config { ssid: SSID.to_vec(), password: vec![], channel: 11 }
}
fn protected_config() -> Config {
Config {
ssid: SSID.to_vec(),
password: vec![0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68],
channel: 11,
}
}
#[test]
fn test_validate_config() {
assert_eq!(
Err(StartResult::InvalidArguments),
validate_config(&Config { ssid: vec![], password: vec![], channel: 15 })
);
assert_eq!(
Err(StartResult::DfsUnsupported),
validate_config(&Config { ssid: vec![], password: vec![], channel: 52 })
);
assert_eq!(
Ok(()),
validate_config(&Config { ssid: vec![], password: vec![], channel: 40 })
);
}
#[test]
fn authenticate_while_sme_is_idle() {
let (mut sme, mut mlme_stream, _) = create_sme();
let client = Client::default();
sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
match mlme_stream.try_next() {
Err(e) => assert_eq!(e.description(), "receiver channel is empty"),
_ => panic!("unexpected event in mlme stream"),
}
}
#[test]
fn ap_starts_success() {
let (mut sme, mut mlme_stream, _) = create_sme();
let mut receiver = sme.on_start_command(unprotected_config());
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::Start(start_req) = msg {
assert_eq!(start_req.ssid, SSID.to_vec());
assert_eq!(start_req.bss_type, fidl_mlme::BssTypes::Infrastructure);
assert_ne!(start_req.beacon_period, 0);
assert_ne!(start_req.dtim_period, 0);
assert_eq!(start_req.channel, unprotected_config().channel);
assert!(start_req.rsne.is_none());
} else {
panic!("expect start AP request to MLME");
}
assert_eq!(Ok(None), receiver.try_recv());
sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCodes::Success));
assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
}
#[test]
fn ap_starts_timeout() {
let (mut sme, _, mut time_stream) = create_sme();
let mut receiver = sme.on_start_command(unprotected_config());
let (_, event) = time_stream.try_next().unwrap().expect("expect timer message");
sme.on_timeout(event);
assert_eq!(Ok(Some(StartResult::TimedOut)), receiver.try_recv());
}
#[test]
fn ap_starts_fails() {
let (mut sme, _, _) = create_sme();
let mut receiver = sme.on_start_command(unprotected_config());
sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCodes::NotSupported));
assert_eq!(Ok(Some(StartResult::InternalError)), receiver.try_recv());
}
#[test]
fn start_req_while_ap_is_starting() {
let (mut sme, _, _) = create_sme();
let mut receiver_one = sme.on_start_command(unprotected_config());
// While SME is starting, any start request receives an error immediately
let mut receiver_two = sme.on_start_command(unprotected_config());
assert_eq!(Ok(Some(StartResult::PreviousStartInProgress)), receiver_two.try_recv());
// Start confirmation for first request should still have an affect
sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCodes::Success));
assert_eq!(Ok(Some(StartResult::Success)), receiver_one.try_recv());
}
#[test]
fn ap_stops_while_idle() {
let (mut sme, _, _) = create_sme();
let mut receiver = sme.on_stop_command();
assert_eq!(Ok(Some(())), receiver.try_recv());
}
#[test]
fn stop_req_while_ap_is_starting() {
let (mut sme, _, _) = create_sme();
let mut start_receiver = sme.on_start_command(unprotected_config());
let mut stop_receiver = sme.on_stop_command();
assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
assert_eq!(Ok(Some(())), stop_receiver.try_recv());
}
#[test]
fn ap_stops_after_started() {
let (mut sme, mut mlme_stream, _) = start_unprotected_ap();
let mut receiver = sme.on_stop_command();
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::Stop(stop_req) => assert_eq!(stop_req.ssid, SSID.to_vec()),
_ => panic!("expect stop AP request to MLME"),
}
assert_eq!(Ok(Some(())), receiver.try_recv());
}
#[test]
fn client_authenticates_supported_authentication_type() {
let (mut sme, mut mlme_stream, _) = start_unprotected_ap();
let client = Client::default();
sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCodes::Success);
}
#[test]
fn client_authenticates_unsupported_authentication_type() {
let (mut sme, mut mlme_stream, _) = start_unprotected_ap();
let client = Client::default();
let auth_ind = client.create_auth_ind(fidl_mlme::AuthenticationTypes::FastBssTransition);
sme.on_mlme_event(auth_ind);
client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCodes::Refused);
}
#[test]
fn client_associates_unprotected_network() {
let (mut sme, mut mlme_stream, _) = start_unprotected_ap();
let client = Client::default();
sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCodes::Success);
sme.on_mlme_event(client.create_assoc_ind(None));
client.verify_assoc_resp(&mut mlme_stream, 1, fidl_mlme::AssociateResultCodes::Success);
}
#[test]
fn client_associates_valid_rsne() {
let (mut sme, mut mlme_stream, _) = start_protected_ap();
let client = Client::default();
client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
sme.on_mlme_event(client.create_assoc_ind(Some(RSNE.to_vec())));
client.verify_assoc_resp(&mut mlme_stream, 1, fidl_mlme::AssociateResultCodes::Success);
client.verify_eapol_req(&mut mlme_stream);
}
#[test]
fn client_associates_invalid_rsne() {
let (mut sme, mut mlme_stream, _) = start_protected_ap();
let client = Client::default();
client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
sme.on_mlme_event(client.create_assoc_ind(None));
client.verify_assoc_resp(
&mut mlme_stream,
0,
fidl_mlme::AssociateResultCodes::RefusedCapabilitiesMismatch,
);
}
#[test]
fn rsn_handshake_timeout() {
let (mut sme, mut mlme_stream, mut time_stream) = start_protected_ap();
let client = Client::default();
client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
sme.on_mlme_event(client.create_assoc_ind(Some(RSNE.to_vec())));
client.verify_assoc_resp(&mut mlme_stream, 1, fidl_mlme::AssociateResultCodes::Success);
for _i in 0..4 {
client.verify_eapol_req(&mut mlme_stream);
let (_, event) = time_stream.try_next().unwrap().expect("expect timer message");
// Calling `on_timeout` with a different event ID is a no-op
let mut fake_event = event.clone();
fake_event.id += 1;
sme.on_timeout(fake_event);
match mlme_stream.try_next() {
Err(e) => assert_eq!(e.description(), "receiver channel is empty"),
_ => panic!("unexpected event in mlme stream"),
}
sme.on_timeout(event);
}
client.verify_deauth_req(&mut mlme_stream, fidl_mlme::ReasonCode::FourwayHandshakeTimeout);
}
#[test]
fn client_restarts_authentication_flow() {
let (mut sme, mut mlme_stream, _) = start_unprotected_ap();
let client = Client::default();
client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
client.associate_and_drain_mlme(&mut sme, &mut mlme_stream, None);
sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCodes::Success);
sme.on_mlme_event(client.create_assoc_ind(None));
client.verify_assoc_resp(&mut mlme_stream, 1, fidl_mlme::AssociateResultCodes::Success);
}
#[test]
fn multiple_clients_associate() {
let (mut sme, mut mlme_stream, _) = start_protected_ap();
let client1 = Client::default();
let client2 = Client { addr: CLIENT_ADDR2 };
sme.on_mlme_event(client1.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
client1.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCodes::Success);
sme.on_mlme_event(client2.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
client2.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCodes::Success);
sme.on_mlme_event(client1.create_assoc_ind(Some(RSNE.to_vec())));
client1.verify_assoc_resp(&mut mlme_stream, 1, fidl_mlme::AssociateResultCodes::Success);
client1.verify_eapol_req(&mut mlme_stream);
sme.on_mlme_event(client2.create_assoc_ind(Some(RSNE.to_vec())));
client2.verify_assoc_resp(&mut mlme_stream, 2, fidl_mlme::AssociateResultCodes::Success);
client2.verify_eapol_req(&mut mlme_stream);
}
fn create_start_conf(result_code: fidl_mlme::StartResultCodes) -> MlmeEvent {
MlmeEvent::StartConf { resp: fidl_mlme::StartConfirm { result_code } }
}
struct Client {
addr: MacAddr,
}
impl Client {
fn default() -> Self {
Client { addr: CLIENT_ADDR }
}
fn authenticate_and_drain_mlme(
&self,
sme: &mut ApSme,
mlme_stream: &mut crate::MlmeStream,
) {
sme.on_mlme_event(self.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::AuthResponse(..) = msg {
// expected path
} else {
panic!("expect auth response to MLME");
}
}
fn associate_and_drain_mlme(
&self,
sme: &mut ApSme,
mlme_stream: &mut crate::MlmeStream,
rsne: Option<Vec<u8>>,
) {
sme.on_mlme_event(self.create_assoc_ind(rsne));
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::AssocResponse(..) = msg {
// expected path
} else {
panic!("expect auth response to MLME");
}
}
fn create_auth_ind(&self, auth_type: fidl_mlme::AuthenticationTypes) -> MlmeEvent {
MlmeEvent::AuthenticateInd {
ind: fidl_mlme::AuthenticateIndication { peer_sta_address: self.addr, auth_type },
}
}
fn create_assoc_ind(&self, rsne: Option<Vec<u8>>) -> MlmeEvent {
MlmeEvent::AssociateInd {
ind: fidl_mlme::AssociateIndication {
peer_sta_address: self.addr,
listen_interval: 100,
ssid: Some(SSID.to_vec()),
rsn: rsne,
},
}
}
fn verify_auth_resp(
&self,
mlme_stream: &mut MlmeStream,
result_code: fidl_mlme::AuthenticateResultCodes,
) {
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::AuthResponse(auth_resp) = msg {
assert_eq!(auth_resp.peer_sta_address, self.addr);
assert_eq!(auth_resp.result_code, result_code);
} else {
panic!("expect auth response to MLME");
}
}
fn verify_assoc_resp(
&self,
mlme_stream: &mut MlmeStream,
aid: AssociationId,
result_code: fidl_mlme::AssociateResultCodes,
) {
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::AssocResponse(assoc_resp) = msg {
assert_eq!(assoc_resp.peer_sta_address, self.addr);
assert_eq!(assoc_resp.association_id, aid);
assert_eq!(assoc_resp.result_code, result_code);
} else {
panic!("expect assoc response to MLME");
}
}
fn verify_eapol_req(&self, mlme_stream: &mut MlmeStream) {
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::Eapol(eapol_req) = msg {
assert_eq!(eapol_req.src_addr, AP_ADDR);
assert_eq!(eapol_req.dst_addr, self.addr);
assert!(eapol_req.data.len() > 0);
} else {
panic!("expect eapol request to MLME");
}
}
fn verify_deauth_req(
&self,
mlme_stream: &mut MlmeStream,
reason_code: fidl_mlme::ReasonCode,
) {
let msg = mlme_stream.try_next().unwrap().expect("expect mlme message");
if let MlmeRequest::Deauthenticate(deauth_req) = msg {
assert_eq!(deauth_req.peer_sta_address, self.addr);
assert_eq!(deauth_req.reason_code, reason_code);
} else {
panic!("expect deauthenticate request to MLME");
}
}
}
fn start_protected_ap() -> (ApSme, crate::MlmeStream, TimeStream) {
start_ap(true)
}
fn start_unprotected_ap() -> (ApSme, crate::MlmeStream, TimeStream) {
start_ap(false)
}
fn start_ap(protected: bool) -> (ApSme, crate::MlmeStream, TimeStream) {
let (mut sme, mut mlme_stream, mut time_stream) = create_sme();
let config = if protected { protected_config() } else { unprotected_config() };
let mut receiver = sme.on_start_command(config);
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::Start(..) => {} // expected path
_ => panic!("expect start AP to MLME"),
}
// drain time stream
while let Ok(..) = time_stream.try_next() {}
sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCodes::Success));
assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
(sme, mlme_stream, time_stream)
}
fn create_sme() -> (ApSme, MlmeStream, TimeStream) {
ApSme::new(DeviceInfo { addr: AP_ADDR, bands: vec![] })
}
}