blob: 0ac253bb3e49b37e9b54e71abfcfe2c4c072a642 [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_common::{self as fidl_common},
fidl_fuchsia_wlan_mlme::{self as fidl_mlme, MlmeEvent},
futures::channel::mpsc,
log::{error},
std::mem,
wlan_common::channel::{Channel, Cbw},
crate::{
clone_utils,
DeviceInfo,
MlmeRequest,
phy_selection::get_device_band_info,
sink::MlmeSink,
timer::TimedEvent,
},
};
const DEFAULT_BEACON_PERIOD: u16 = 1000;
const DEFAULT_DTIM_PERIOD: u8 = 1;
// A token is an opaque value that identifies a particular request from a user.
// To avoid parameterizing over many different token types, we introduce a helper
// trait that enables us to group them into a single generic parameter.
pub trait Tokens {
type JoinToken;
type LeaveToken;
}
mod internal {
pub type UserSink<T> = crate::sink::UnboundedSink<super::UserEvent<T>>;
}
use self::internal::*;
pub type UserStream<T> = mpsc::UnboundedReceiver<UserEvent<T>>;
// A list of pending join/leave requests to be maintained in the intermediate
// 'Joining'/'Leaving' states where we are waiting for a reply from MLME and cannot
// serve the requests immediately.
struct PendingRequests<T: Tokens> {
leave: Vec<T::LeaveToken>,
join: Option<(T::JoinToken, Config)>,
}
impl<T: Tokens> PendingRequests<T> {
pub fn new() -> Self {
PendingRequests { leave: Vec::new(), join: None }
}
pub fn enqueue_leave(&mut self, user_sink: &UserSink<T>, token: T::LeaveToken) {
self.replace_join_request(user_sink, None);
self.leave.push(token);
}
pub fn enqueue_join(&mut self, user_sink: &UserSink<T>, token: T::JoinToken, config: Config) {
self.replace_join_request(user_sink, Some((token, config)));
}
pub fn is_empty(&self) -> bool {
self.leave.is_empty() && self.join.is_none()
}
fn replace_join_request(
&mut self,
user_sink: &UserSink<T>,
req: Option<(T::JoinToken, Config)>)
{
if let Some((old_token, _)) = mem::replace(&mut self.join, req) {
report_join_finished(user_sink, old_token, JoinMeshResult::Canceled);
}
}
}
enum State<T: Tokens> {
Idle,
Joining {
token: T::JoinToken,
config: Config,
pending: PendingRequests<T>,
},
Joined {
config: Config,
},
Leaving {
config: Config,
pending: PendingRequests<T>,
}
}
pub struct MeshSme<T: Tokens> {
mlme_sink: MlmeSink,
user_sink: UserSink<T>,
state: Option<State<T>>,
device_info: DeviceInfo,
}
pub type MeshId = Vec<u8>;
#[derive(Debug)]
pub struct Config {
pub mesh_id: MeshId,
pub channel: u8,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum JoinMeshResult {
Success,
Canceled,
InternalError,
InvalidArguments,
DfsUnsupported,
}
#[derive(Clone, Copy, Debug)]
pub enum LeaveMeshResult {
Success,
InternalError,
}
// A message from the Mesh node to a user or a group of listeners
#[derive(Debug)]
pub enum UserEvent<T: Tokens> {
JoinMeshFinished {
token: T::JoinToken,
result: JoinMeshResult,
},
LeaveMeshFinished {
token: T::LeaveToken,
result: LeaveMeshResult,
}
}
impl<T: Tokens> MeshSme<T> {
pub fn on_join_command(&mut self, token: T::JoinToken, config: Config) {
if let Err(result) = validate_config(&config) {
report_join_finished(&self.user_sink, token, result);
return;
}
self.state = Some(match self.state.take().unwrap() {
State::Idle => {
self.mlme_sink.send(MlmeRequest::Start(create_start_request(&config)));
State::Joining { token, pending: PendingRequests::new(), config }
},
State::Joining { token: cur_token, config: cur_config, mut pending } => {
pending.enqueue_join(&self.user_sink, token, config);
State::Joining { token: cur_token, config: cur_config, pending }
},
State::Joined { config: cur_config } => {
self.mlme_sink.send(MlmeRequest::Stop(create_stop_request()));
let mut pending = PendingRequests::new();
pending.enqueue_join(&self.user_sink, token, config);
State::Leaving { config: cur_config, pending }
},
State::Leaving { config: cur_config, mut pending } => {
pending.enqueue_join(&self.user_sink, token, config);
State::Leaving { config: cur_config, pending }
}
});
}
pub fn on_leave_command(&mut self, token: T::LeaveToken) {
self.state = Some(match self.state.take().unwrap() {
State::Idle => {
report_leave_finished(&self.user_sink, token, LeaveMeshResult::Success);
State::Idle
},
State::Joining { token: cur_token, config, mut pending } => {
pending.enqueue_leave(&self.user_sink, token);
State::Joining { token: cur_token, pending, config }
},
State::Joined { config } => {
self.mlme_sink.send(MlmeRequest::Stop(create_stop_request()));
let mut pending = PendingRequests::new();
pending.enqueue_leave(&self.user_sink, token);
State::Leaving { config, pending }
},
State::Leaving { config, mut pending } => {
pending.enqueue_leave(&self.user_sink, token);
State::Leaving { config, pending }
}
});
}
}
fn on_back_to_idle<T: Tokens>(
pending: PendingRequests<T>,
user_sink: &UserSink<T>,
mlme_sink: &MlmeSink
) -> State<T> {
for token in pending.leave {
report_leave_finished(user_sink, token, LeaveMeshResult::Success);
}
if let Some((token, config)) = pending.join {
mlme_sink.send(MlmeRequest::Start(create_start_request(&config)));
State::Joining { token, config, pending: PendingRequests::new() }
} else {
State::Idle
}
}
fn validate_config(config: &Config) -> Result<(), JoinMeshResult> {
let c = Channel::new(config.channel.clone(), Cbw::Cbw20);
if !c.is_valid() {
Err(JoinMeshResult::InvalidArguments)
} else if c.is_dfs() {
Err(JoinMeshResult::DfsUnsupported)
} else {
Ok(())
}
}
fn create_start_request(config: &Config) -> fidl_mlme::StartRequest {
fidl_mlme::StartRequest {
ssid: vec![],
bss_type: fidl_mlme::BssTypes::Mesh,
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: None,
mesh_id: config.mesh_id.clone(),
phy: fidl_common::Phy::Ht, // TODO(WLAN-908, WLAN-909): Use dynamic value
cbw: fidl_common::Cbw::Cbw20,
}
}
fn create_stop_request() -> fidl_mlme::StopRequest {
fidl_mlme::StopRequest { ssid: vec![], }
}
impl<T: Tokens> super::Station for MeshSme<T> {
type Event = ();
fn on_mlme_event(&mut self, event: MlmeEvent) {
self.state = Some(match self.state.take().unwrap() {
State::Idle => State::Idle,
State::Joining { token, pending, config } => match event {
MlmeEvent::StartConf { resp } => match resp.result_code {
fidl_mlme::StartResultCodes::Success => {
report_join_finished(&self.user_sink, token, JoinMeshResult::Success);
if pending.is_empty() {
State::Joined { config }
} else {
// If there are any pending join/leave commands that arrived while we
// were waiting for 'Start' to complete, then start leaving immediately,
// and then process the pending commands once the 'Stop' call completes.
self.mlme_sink.send(MlmeRequest::Stop(create_stop_request()));
State::Leaving { config, pending }
}
},
other => {
error!("failed to join mesh: {:?}", other);
report_join_finished(&self.user_sink, token, JoinMeshResult::InternalError);
on_back_to_idle(pending, &self.user_sink, &self.mlme_sink)
}
},
_ => State::Joining { token, pending, config },
},
State::Joined { config } => match event {
MlmeEvent::IncomingMpOpenAction { action } => {
// TODO(gbonik): implement a proper MPM state machine
println!("received an MPM Open action: {:?}", action);
if mesh_profile_matches(&config.mesh_id, &get_mesh_config(),
&action.common.mesh_id, &action.common.mesh_config) {
let aid = 1;
if let Some(params) = create_peering_params(
&self.device_info, &config, &action.common, aid)
{
self.mlme_sink.send(MlmeRequest::MeshPeeringEstablished(params));
// TODO(gbonik): actually fill out the data correctly
// instead of being a copycat
let open = fidl_mlme::MeshPeeringOpenAction {
common: fidl_mlme::MeshPeeringCommon {
local_link_id: 0,
.. clone_utils::clone_mesh_peering_common(&action.common)
},
};
self.mlme_sink.send(MlmeRequest::SendMpOpenAction(open));
let conf = fidl_mlme::MeshPeeringConfirmAction {
common: fidl_mlme::MeshPeeringCommon {
local_link_id: 0,
.. action.common
},
peer_link_id: action.common.local_link_id,
aid,
};
self.mlme_sink.send(MlmeRequest::SendMpConfirmAction(conf));
}
}
State::Joined { config }
},
_ => State::Joined { config },
},
State::Leaving { config, pending } => match event {
MlmeEvent::StopConf { resp } => match resp.result_code {
fidl_mlme::StopResultCodes::Success =>
on_back_to_idle(pending, &self.user_sink, &self.mlme_sink),
other => {
error!("failed to leave mesh: {:?}", other);
for token in pending.leave {
report_leave_finished(
&self.user_sink, token, LeaveMeshResult::InternalError);
}
if let Some((token, _)) = pending.join {
report_join_finished(
&self.user_sink, token, JoinMeshResult::InternalError);
}
State::Joined { config }
}
},
_ => State::Leaving { config, pending }
}
});
}
fn on_timeout(&mut self, _timed_event: TimedEvent<()>) {
unimplemented!();
}
}
fn create_peering_params(device_info: &DeviceInfo,
config: &Config,
peer: &fidl_mlme::MeshPeeringCommon,
local_aid: u16)
-> Option<fidl_mlme::MeshPeeringParams>
{
let band_caps = match get_device_band_info(device_info, config.channel) {
Some(x) => x,
None => {
error!("Failed to find band capabilities for channel {}", config.channel);
return None;
}
};
let rates = peer.rates.iter().filter(|x| band_caps.basic_rates.contains(x)).cloned().collect();
Some(fidl_mlme::MeshPeeringParams {
peer_sta_address: peer.peer_sta_address,
local_aid,
rates
})
}
fn report_join_finished<T: Tokens>(user_sink: &UserSink<T>,
token: T::JoinToken,
result: JoinMeshResult)
{
user_sink.send(UserEvent::JoinMeshFinished { token, result });
}
fn report_leave_finished<T: Tokens>(user_sink: &UserSink<T>, token: T::LeaveToken,
result: LeaveMeshResult)
{
user_sink.send(UserEvent::LeaveMeshFinished { token, result });
}
impl<T: Tokens> MeshSme<T> {
pub fn new(device_info: DeviceInfo) -> (Self, crate::MlmeStream, UserStream<T>) {
let (mlme_sink, mlme_stream) = mpsc::unbounded();
let (user_sink, user_stream) = mpsc::unbounded();
let sme = MeshSme {
mlme_sink: MlmeSink::new(mlme_sink),
user_sink: UserSink::new(user_sink),
state: Some(State::Idle),
device_info,
};
(sme, mlme_stream, user_stream)
}
}
fn mesh_profile_matches(our_mesh_id: &[u8],
ours: &fidl_mlme::MeshConfiguration,
their_mesh_id: &[u8],
theirs: &fidl_mlme::MeshConfiguration) -> bool {
// IEEE Std 802.11-2016, 14.2.3
their_mesh_id == our_mesh_id
&& theirs.active_path_sel_proto_id == ours.active_path_sel_proto_id
&& theirs.active_path_sel_metric_id == ours.active_path_sel_metric_id
&& theirs.congest_ctrl_method_id == ours.congest_ctrl_method_id
&& theirs.sync_method_id == ours.sync_method_id
&& theirs.auth_proto_id == ours.auth_proto_id
}
fn get_mesh_config() -> fidl_mlme::MeshConfiguration {
fidl_mlme::MeshConfiguration {
active_path_sel_proto_id: 1, // HWMP
active_path_sel_metric_id: 1, // Airtime
congest_ctrl_method_id: 0, // Inactive
sync_method_id: 1, // Neighbor offset sync
auth_proto_id: 0, // No auth
mesh_formation_info: 0,
mesh_capability: 0x9, // accept additional peerings (0x1) + forwarding (0x8)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_config() {
assert_eq!(Err(JoinMeshResult::InvalidArguments),
validate_config(&Config { mesh_id: vec![], channel: 15}));
assert_eq!(Err(JoinMeshResult::DfsUnsupported),
validate_config(&Config { mesh_id: vec![], channel: 52}));
assert_eq!(Ok(()),
validate_config(&Config { mesh_id: vec![], channel: 40}));
}
}