blob: b74f6a1c2ffe5dc2f92ac4cd9a5b1f616a463be1 [file] [log] [blame]
// Copyright 2017 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.
#include <wlan/mlme/client/station.h>
#include <wlan/common/channel.h>
#include <wlan/common/energy.h>
#include <wlan/common/logging.h>
#include <wlan/common/stats.h>
#include <wlan/mlme/debug.h>
#include <wlan/mlme/device_interface.h>
#include <wlan/mlme/mac_frame.h>
#include <wlan/mlme/packet.h>
#include <wlan/mlme/sequence.h>
#include <wlan/mlme/service.h>
#include <wlan/mlme/timer.h>
#include <fuchsia/wlan/mlme/c/fidl.h>
#include <cstring>
#include <utility>
namespace wlan {
namespace wlan_mlme = ::fuchsia::wlan::mlme;
namespace wlan_stats = ::fuchsia::wlan::stats;
using common::dBm;
// TODO(hahnr): Revisit frame construction to reduce boilerplate code.
static constexpr size_t kAssocBcnCountTimeout = 20;
static constexpr size_t kSignalReportBcnCountTimeout = 10;
Station::Station(DeviceInterface* device, fbl::unique_ptr<Timer> timer)
: device_(device), timer_(std::move(timer)) {
(void)assoc_timeout_;
bssid_.Reset();
}
void Station::Reset() {
debugfn();
timer_->CancelTimer();
state_ = WlanState::kUnjoined;
bss_.reset();
join_timeout_ = zx::time();
auth_timeout_ = zx::time();
last_seen_ = zx::time();
bssid_.Reset();
}
zx_status_t Station::HandleMlmeMessage(uint32_t ordinal) {
WLAN_STATS_INC(svc_msg.in);
// Always allow MLME-JOIN.request.
if (ordinal == fuchsia_wlan_mlme_MLMEJoinReqOrdinal) { return ZX_OK; }
// Drop other MLME requests if there is no BSSID set yet.
return (bssid() == nullptr ? ZX_ERR_STOP : ZX_OK);
}
zx_status_t Station::HandleMlmeJoinReq(const MlmeMsg<wlan_mlme::JoinRequest>& req) {
debugfn();
if (state_ != WlanState::kUnjoined) {
warnf("already joined; resetting station\n");
Reset();
}
// Clone request to take ownership of the BSS.
bss_ = wlan_mlme::BSSDescription::New();
req.body()->selected_bss.Clone(bss_.get());
bssid_.Set(bss_->bssid.data());
auto chan = GetBssChan();
// TODO(NET-449): Move this logic to policy engine
// Validation and sanitization
if (!common::IsValidChan(chan)) {
wlan_channel_t chan_sanitized = chan;
chan_sanitized.cbw = common::GetValidCbw(chan);
errorf("Wlanstack attempts to configure an invalid channel: %s. Falling back to %s\n",
common::ChanStr(chan).c_str(), common::ChanStr(chan_sanitized).c_str());
chan = chan_sanitized;
}
if (IsCbw40RxReady()) {
// Override with CBW40 support
wlan_channel_t chan_override = chan;
chan_override.cbw = CBW40;
chan_override.cbw = common::GetValidCbw(chan_override);
infof("CBW40 Rx is ready. Overriding the channel configuration from %s to %s\n",
common::ChanStr(chan).c_str(), common::ChanStr(chan_override).c_str());
chan = chan_override;
}
debugjoin("setting channel to %s\n", common::ChanStr(chan).c_str());
zx_status_t status = device_->SetChannel(chan);
if (status != ZX_OK) {
errorf("could not set wlan channel to %s (status %d)\n", common::ChanStr(chan).c_str(),
status);
Reset();
service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::JOIN_FAILURE_TIMEOUT);
return status;
}
join_chan_ = chan;
join_timeout_ = deadline_after_bcn_period(req.body()->join_failure_timeout);
status = timer_->SetTimer(join_timeout_);
if (status != ZX_OK) {
errorf("could not set join timer: %d\n", status);
Reset();
service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::JOIN_FAILURE_TIMEOUT);
}
// TODO(hahnr): Update when other BSS types are supported.
wlan_bss_config_t cfg{
.bss_type = WLAN_BSS_TYPE_INFRASTRUCTURE,
.remote = true,
};
bssid_.CopyTo(cfg.bssid);
device_->ConfigureBss(&cfg);
return status;
} // namespace wlan
zx_status_t Station::HandleMlmeAuthReq(const MlmeMsg<wlan_mlme::AuthenticateRequest>& req) {
debugfn();
if (bss_ == nullptr) { return ZX_ERR_BAD_STATE; }
// TODO(tkilbourn): better result codes
common::MacAddr peer_sta_addr(req.body()->peer_sta_address.data());
if (bssid_ != peer_sta_addr) {
errorf("cannot authenticate before joining\n");
return service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::REFUSED);
}
if (state_ == WlanState::kUnjoined) {
errorf("must join before authenticating\n");
return service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::REFUSED);
}
if (state_ != WlanState::kUnauthenticated) {
warnf("already authenticated; sending request anyway\n");
}
if (req.body()->auth_type != wlan_mlme::AuthenticationTypes::OPEN_SYSTEM) {
// TODO(tkilbourn): support other authentication types
// TODO(tkilbourn): set the auth_alg_ when we support other authentication types
errorf("only OpenSystem authentication is supported\n");
return service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::REFUSED);
}
debugjoin("authenticating to %s\n", MACSTR(bssid_));
MgmtFrame<Authentication> frame;
auto status = BuildMgmtFrame(&frame);
if (status != ZX_OK) {
errorf("authing: failed to build a frame\n");
return status;
}
auto hdr = frame.hdr();
const common::MacAddr& mymac = device_->GetState()->address();
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
SetSeqNo(hdr, &seq_);
frame.FillTxInfo();
// TODO(tkilbourn): this assumes Open System authentication
auto auth = frame.body();
auth->auth_algorithm_number = auth_alg_;
auth->auth_txn_seq_number = 1;
auth->status_code = 0; // Reserved, so set to 0
finspect("Outbound Mgmt Frame(Auth): %s\n", debug::Describe(*hdr).c_str());
status = device_->SendWlan(frame.Take());
if (status != ZX_OK) {
errorf("could not send auth packet: %d\n", status);
service::SendAuthConfirm(device_, bssid_, wlan_mlme::AuthenticateResultCodes::REFUSED);
return status;
}
auth_timeout_ = deadline_after_bcn_period(req.body()->auth_failure_timeout);
status = timer_->SetTimer(auth_timeout_);
if (status != ZX_OK) {
errorf("could not set auth timer: %d\n", status);
// This is the wrong result code, but we need to define our own codes at some later time.
service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::AUTH_FAILURE_TIMEOUT);
// TODO(tkilbourn): reset the station?
}
return status;
}
zx_status_t Station::HandleMlmeDeauthReq(const MlmeMsg<wlan_mlme::DeauthenticateRequest>& req) {
debugfn();
if (state_ != WlanState::kAssociated && state_ != WlanState::kAuthenticated) {
errorf("not associated or authenticated; ignoring deauthenticate request\n");
return ZX_OK;
}
if (bss_ == nullptr) { return ZX_ERR_BAD_STATE; }
// Check whether the request wants to deauthenticate from this STA's BSS.
common::MacAddr peer_sta_addr(req.body()->peer_sta_address.data());
if (bssid_ != peer_sta_addr) { return ZX_OK; }
MgmtFrame<Deauthentication> frame;
auto status = BuildMgmtFrame(&frame);
if (status != ZX_OK) { return status; }
auto hdr = frame.hdr();
const common::MacAddr& mymac = device_->GetState()->address();
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
SetSeqNo(hdr, &seq_);
frame.FillTxInfo();
auto deauth = frame.body();
deauth->reason_code = static_cast<uint16_t>(req.body()->reason_code);
finspect("Outbound Mgmt Frame(Deauth): %s\n", debug::Describe(*hdr).c_str());
status = device_->SendWlan(frame.Take());
if (status != ZX_OK) {
errorf("could not send deauth packet: %d\n", status);
// Deauthenticate nevertheless. IEEE isn't clear on what we are supposed to do.
}
infof("deauthenticating from %s, reason=%hu\n", bss_->ssid->data(), req.body()->reason_code);
// TODO(hahnr): Refactor once we have the new state machine.
state_ = WlanState::kUnauthenticated;
device_->SetStatus(0);
controlled_port_ = eapol::PortState::kBlocked;
service::SendDeauthConfirm(device_, bssid_);
return ZX_OK;
}
zx_status_t Station::HandleMlmeAssocReq(const MlmeMsg<wlan_mlme::AssociateRequest>& req) {
debugfn();
if (bss_ == nullptr) { return ZX_ERR_BAD_STATE; }
// TODO(tkilbourn): better result codes
common::MacAddr peer_sta_addr(req.body()->peer_sta_address.data());
if (bssid_ != peer_sta_addr) {
errorf("bad peer STA address for association\n");
return service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::REFUSED);
}
if (state_ == WlanState::kUnjoined || state_ == WlanState::kUnauthenticated) {
errorf("must authenticate before associating\n");
return service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::REFUSED);
}
if (state_ == WlanState::kAssociated) {
warnf("already authenticated; sending request anyway\n");
}
debugjoin("associating to %s\n", MACSTR(bssid_));
size_t body_payload_len = 128;
MgmtFrame<AssociationRequest> frame;
auto status = BuildMgmtFrame(&frame, body_payload_len);
if (status != ZX_OK) { return status; }
// TODO(tkilbourn): a lot of this is hardcoded for now. Use device capabilities to set up the
// request.
auto hdr = frame.hdr();
const common::MacAddr& mymac = device_->GetState()->address();
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
SetSeqNo(hdr, &seq_);
frame.FillTxInfo();
auto assoc = frame.body();
assoc->cap.set_ess(1);
assoc->cap.set_short_preamble(0); // For robustness. TODO(porce): Enforce Ralink
assoc->listen_interval = 0;
ElementWriter w(assoc->elements,
frame.len() - sizeof(MgmtFrameHeader) - sizeof(AssociationRequest));
if (!w.write<SsidElement>(bss_->ssid->data())) {
errorf("could not write ssid \"%s\" to association request\n", bss_->ssid->data());
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
// TODO(tkilbourn): determine these rates based on hardware and the AP
std::vector<uint8_t> rates = {0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24};
if (!w.write<SupportedRatesElement>(std::move(rates))) {
errorf("could not write supported rates\n");
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
std::vector<uint8_t> ext_rates = {0x30, 0x48, 0x60, 0x6c};
if (!w.write<ExtendedSupportedRatesElement>(std::move(ext_rates))) {
errorf("could not write extended supported rates\n");
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
// Write RSNE from MLME-Association.request if available.
if (req.body()->rsn) {
if (!w.write<RsnElement>(req.body()->rsn->data(), req.body()->rsn->size())) {
return ZX_ERR_IO;
}
}
if (IsHTReady()) {
HtCapabilities htc = BuildHtCapabilities();
if (!w.write<HtCapabilities>(htc.ht_cap_info, htc.ampdu_params, htc.mcs_set, htc.ht_ext_cap,
htc.txbf_cap, htc.asel_cap)) {
errorf("could not write HtCapabilities\n");
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
}
// Validate the request in debug mode
ZX_DEBUG_ASSERT(assoc->Validate(w.size()));
size_t body_len = sizeof(AssociationRequest) + w.size();
status = frame.set_body_len(body_len);
if (status != ZX_OK) {
errorf("could not set body length to %zu: %d\n", body_len, status);
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return status;
}
finspect("Outbound Mgmt Frame (AssocReq): %s\n", debug::Describe(*hdr).c_str());
status = device_->SendWlan(frame.Take());
if (status != ZX_OK) {
errorf("could not send assoc packet: %d\n", status);
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return status;
}
// TODO(tkilbourn): get the assoc timeout from somewhere
assoc_timeout_ = deadline_after_bcn_period(kAssocBcnCountTimeout);
status = timer_->SetTimer(assoc_timeout_);
if (status != ZX_OK) {
errorf("could not set auth timer: %d\n", status);
// This is the wrong result code, but we need to define our own codes at some later time.
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
// TODO(tkilbourn): reset the station?
}
return status;
}
zx_status_t Station::HandleMgmtFrame(const MgmtFrameHeader& hdr) {
WLAN_STATS_INC(mgmt_frame.in);
// Drop management frames if either, there is no BSSID set yet,
// or the frame is not from the BSS.
if (bssid() == nullptr || *bssid() != hdr.addr3) {
WLAN_STATS_INC(mgmt_frame.drop);
return ZX_ERR_STOP;
}
WLAN_STATS_INC(mgmt_frame.out);
return ZX_OK;
}
// TODO(hahnr): Support ProbeResponses.
zx_status_t Station::HandleBeacon(const MgmtFrame<Beacon>& frame) {
debugfn();
ZX_DEBUG_ASSERT(bss_ != nullptr);
avg_rssi_dbm_.add(dBm(frame.View().rx_info()->rssi_dbm));
// TODO(tkilbourn): update any other info (like rolling average of rssi)
last_seen_ = timer_->Now();
if (join_timeout_ > zx::time()) {
join_timeout_ = zx::time();
timer_->CancelTimer();
state_ = WlanState::kUnauthenticated;
debugjoin("joined %s\n", bss_->ssid->data());
return service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::SUCCESS);
}
auto bcn = frame.body();
size_t elt_len = frame.body_len() - sizeof(Beacon);
ElementReader reader(bcn->elements, elt_len);
while (reader.is_valid()) {
const ElementHeader* hdr = reader.peek();
if (hdr == nullptr) break;
switch (hdr->id) {
case element_id::kTim: {
auto tim = reader.read<TimElement>();
if (tim == nullptr) goto done_iter;
// Do not process the Beacon's TIM element unless the client is associated.
if (state_ != WlanState::kAssociated) { continue; }
if (tim->traffic_buffered(aid_)) { SendPsPoll(); }
break;
}
default:
reader.skip(sizeof(ElementHeader) + hdr->len);
break;
}
}
done_iter:
return ZX_OK;
}
zx_status_t Station::HandleAuthentication(const MgmtFrame<Authentication>& frame) {
debugfn();
if (state_ != WlanState::kUnauthenticated) {
// TODO(tkilbourn): should we process this Authentication packet anyway? The spec is
// unclear.
debugjoin("unexpected authentication frame\n");
return ZX_OK;
}
auto auth = frame.body();
if (auth->auth_algorithm_number != auth_alg_) {
errorf("mismatched authentication algorithm (expected %u, got %u)\n", auth_alg_,
auth->auth_algorithm_number);
return ZX_ERR_BAD_STATE;
}
// TODO(tkilbourn): this only makes sense for Open System.
if (auth->auth_txn_seq_number != 2) {
errorf("unexpected auth txn sequence number (expected 2, got %u)\n",
auth->auth_txn_seq_number);
return ZX_ERR_BAD_STATE;
}
if (auth->status_code != status_code::kSuccess) {
errorf("authentication failed (status code=%u)\n", auth->status_code);
// TODO(tkilbourn): is this the right result code?
service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::AUTHENTICATION_REJECTED);
auth_timeout_ = zx::time();
return ZX_ERR_BAD_STATE;
}
common::MacAddr bssid(bss_->bssid.data());
debugjoin("authenticated to %s\n", MACSTR(bssid));
state_ = WlanState::kAuthenticated;
auth_timeout_ = zx::time();
timer_->CancelTimer();
service::SendAuthConfirm(device_, bssid_, wlan_mlme::AuthenticateResultCodes::SUCCESS);
return ZX_OK;
}
zx_status_t Station::HandleDeauthentication(const MgmtFrame<Deauthentication>& frame) {
debugfn();
if (state_ != WlanState::kAssociated && state_ != WlanState::kAuthenticated) {
debugjoin("got spurious deauthenticate; ignoring\n");
return ZX_OK;
}
auto deauth = frame.body();
infof("deauthenticating from %s, reason=%hu\n", bss_->ssid->data(), deauth->reason_code);
state_ = WlanState::kUnauthenticated;
device_->SetStatus(0);
controlled_port_ = eapol::PortState::kBlocked;
return service::SendDeauthIndication(device_, bssid_,
static_cast<wlan_mlme::ReasonCode>(deauth->reason_code));
}
zx_status_t Station::HandleAssociationResponse(const MgmtFrame<AssociationResponse>& frame) {
debugfn();
if (state_ != WlanState::kAuthenticated) {
// TODO(tkilbourn): should we process this Association response packet anyway? The spec is
// unclear.
debugjoin("unexpected association response frame\n");
return ZX_OK;
}
auto assoc = frame.body();
if (assoc->status_code != status_code::kSuccess) {
errorf("association failed (status code=%u)\n", assoc->status_code);
// TODO(tkilbourn): map to the correct result code
service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_BAD_STATE;
}
common::MacAddr bssid(bss_->bssid.data());
state_ = WlanState::kAssociated;
assoc_timeout_ = zx::time();
aid_ = assoc->aid & kAidMask;
timer_->CancelTimer();
service::SendAssocConfirm(device_, wlan_mlme::AssociateResultCodes::SUCCESS, aid_);
signal_report_timeout_ = deadline_after_bcn_period(kSignalReportBcnCountTimeout);
timer_->SetTimer(signal_report_timeout_);
avg_rssi_dbm_.reset();
avg_rssi_dbm_.add(dBm(frame.View().rx_info()->rssi_dbm));
service::SendSignalReportIndication(device_, common::dBm(frame.View().rx_info()->rssi_dbm));
// Open port if user connected to an open network.
if (bss_->rsn.is_null()) {
debugjoin("802.1X controlled port is now open\n");
controlled_port_ = eapol::PortState::kOpen;
device_->SetStatus(ETH_STATUS_ONLINE);
}
const common::MacAddr& mymac = device_->GetState()->address();
infof("NIC %s associated with \"%s\"(%s) in channel %s, %s, %s\n", mymac.ToString().c_str(),
bss_->ssid->data(), bssid.ToString().c_str(), common::ChanStr(GetJoinChan()).c_str(),
common::BandStr(GetJoinChan()).c_str(), IsHTReady() ? "802.11n HT" : "802.11g/a");
// TODO(porce): Time when to establish BlockAck session
// Handle MLME-level retry, if MAC-level retry ultimately fails
// Wrap this as EstablishBlockAckSession(peer_mac_addr)
// Signal to lower MAC for proper session handling
SendAddBaRequestFrame();
return ZX_OK;
}
zx_status_t Station::HandleDisassociation(const MgmtFrame<Disassociation>& frame) {
debugfn();
if (state_ != WlanState::kAssociated) {
debugjoin("got spurious disassociate; ignoring\n");
return ZX_OK;
}
auto disassoc = frame.body();
common::MacAddr bssid(bss_->bssid.data());
infof("disassociating from %s(%s), reason=%u\n", MACSTR(bssid), bss_->ssid->data(),
disassoc->reason_code);
state_ = WlanState::kAuthenticated;
device_->SetStatus(0);
controlled_port_ = eapol::PortState::kBlocked;
signal_report_timeout_ = zx::time();
timer_->CancelTimer();
return service::SendDisassociateIndication(device_, bssid, disassoc->reason_code);
}
zx_status_t Station::HandleAddBaRequestFrame(const MgmtFrame<AddBaRequestFrame>& rx_frame) {
debugfn();
auto addbar = rx_frame.body();
finspect("Inbound ADDBA Req frame: len %zu\n", rx_frame.body_len());
finspect(" addba req: %s\n", debug::Describe(*addbar).c_str());
// Construct AddBaResponse frame
MgmtFrame<AddBaResponseFrame> frame;
auto status = BuildMgmtFrame(&frame);
if (status != ZX_OK) { return status; }
auto hdr = frame.hdr();
const common::MacAddr& mymac = device_->GetState()->address();
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
SetSeqNo(hdr, &seq_);
frame.FillTxInfo();
auto resp = frame.body();
resp->category = action::Category::kBlockAck;
resp->action = action::BaAction::kAddBaResponse;
resp->dialog_token = addbar->dialog_token;
// TODO(porce): Implement DelBa as a response to AddBar for decline
// Note: Returning AddBaResponse with status_code::kRefused seems ineffective.
// ArubaAP is persistent not honoring that.
resp->status_code = status_code::kSuccess;
// TODO(porce): Query the radio chipset capability to build the response.
// TODO(NET-567): Use the outcome of the association negotiation
resp->params.set_amsdu(addbar->params.amsdu() == 1 && IsAmsduRxReady());
resp->params.set_policy(BlockAckParameters::kImmediate);
resp->params.set_tid(addbar->params.tid());
// TODO(porce): Once chipset capability is ready, refactor below buffer_size
// calculation.
auto buffer_size_ap = addbar->params.buffer_size();
constexpr size_t buffer_size_ralink = 64;
auto buffer_size = (buffer_size_ap <= buffer_size_ralink) ? buffer_size_ap : buffer_size_ralink;
resp->params.set_buffer_size(buffer_size);
resp->timeout = addbar->timeout;
finspect("Outbound ADDBA Resp frame: len %zu\n", frame.len());
finspect("Outbound Mgmt Frame(ADDBA Resp): %s\n", debug::Describe(*hdr).c_str());
status = device_->SendWlan(frame.Take());
if (status != ZX_OK) {
errorf("could not send AddBaResponse: %d\n", status);
return status;
}
return ZX_OK;
}
zx_status_t Station::HandleAddBaResponseFrame(const MgmtFrame<AddBaResponseFrame>& rx_frame) {
debugfn();
auto addba_resp = rx_frame.body();
finspect("Inbound ADDBA Resp frame: len %zu\n", rx_frame.body_len());
finspect(" addba resp: %s\n", debug::Describe(*addba_resp).c_str());
// TODO(porce): Keep the result of negotiation.
return ZX_OK;
}
zx_status_t Station::HandleDataFrame(const DataFrameHeader& hdr) {
WLAN_STATS_INC(data_frame.in);
if (state_ != WlanState::kAssociated) { return ZX_OK; }
auto from_bss = (bssid() != nullptr && *bssid() == hdr.addr2);
if (!from_bss) { return ZX_ERR_STOP; }
return ZX_OK;
}
zx_status_t Station::HandleNullDataFrame(const DataFrame<NilHeader>& frame) {
debugfn();
ZX_DEBUG_ASSERT(bssid() != nullptr);
ZX_DEBUG_ASSERT(state_ == WlanState::kAssociated);
// Take signal strength into account.
avg_rssi_dbm_.add(dBm(frame.View().rx_info()->rssi_dbm));
// Some AP's such as Netgear Routers send periodic NULL data frames to test whether a client
// timed out. The client must respond with a NULL data frame itself to not get
// deauthenticated.
SendKeepAliveResponse();
return ZX_OK;
}
zx_status_t Station::HandleDataFrame(const DataFrame<LlcHeader>& frame) {
debugfn();
if (kFinspectEnabled) { DumpDataFrame(frame); }
auto associated = (state_ == WlanState::kAssociated);
if (!associated) {
debugf("dropping data packet while not associated\n");
return ZX_ERR_STOP;
}
ZX_DEBUG_ASSERT(bssid() != nullptr);
ZX_DEBUG_ASSERT(state_ == WlanState::kAssociated);
switch (frame.hdr()->fc.subtype()) {
case DataSubtype::kDataSubtype:
break;
case DataSubtype::kQosdata: // For data frames within BlockAck session.
ZX_DEBUG_ASSERT(frame.hdr()->HasQosCtrl());
ZX_DEBUG_ASSERT(frame.hdr()->qos_ctrl() != nullptr);
break;
default:
warnf("unsupported data subtype %02x\n", frame.hdr()->fc.subtype());
return ZX_OK;
}
// Take signal strength into account.
avg_rssi_dbm_.add(dBm(frame.View().rx_info()->rssi_dbm));
auto hdr = frame.hdr();
auto llc = frame.body();
// Forward EAPOL frames to SME.
size_t payload_len = frame.body_len() - sizeof(LlcHeader);
if (be16toh(llc->protocol_id) == kEapolProtocolId) {
if (payload_len < sizeof(EapolFrame)) {
warnf("short EAPOL frame; len = %zu", payload_len);
return ZX_OK;
}
auto eapol = reinterpret_cast<const EapolFrame*>(llc->payload);
uint16_t actual_body_len = payload_len;
uint16_t expected_body_len = be16toh(eapol->packet_body_length);
if (actual_body_len >= expected_body_len) {
return service::SendEapolIndication(device_, *eapol, hdr->addr3, hdr->addr1);
}
return ZX_OK;
}
// Drop packets if RSNA was not yet established.
if (controlled_port_ == eapol::PortState::kBlocked) { return ZX_OK; }
// PS-POLL if there are more buffered unicast frames.
if (hdr->fc.more_data() && hdr->addr1.IsUcast()) { SendPsPoll(); }
if (frame.hdr()->fc.subtype() == DataSubtype::kQosdata &&
hdr->qos_ctrl()->amsdu_present() == 1) {
// TODO(porce): Adopt FrameHandler 2.0
return HandleAmsduFrame(frame);
}
ZX_DEBUG_ASSERT(frame.body_len() >= sizeof(LlcHeader));
if (frame.body_len() < sizeof(LlcHeader)) {
errorf("Inbound LLC frame too short (%zu bytes). Drop.", frame.body_len());
return ZX_ERR_STOP;
}
return HandleLlcFrame(*llc, frame.body_len(), hdr->addr1, hdr->addr3);
}
zx_status_t Station::HandleLlcFrame(const LlcHeader& llc_frame, size_t llc_frame_len,
const common::MacAddr& dest, const common::MacAddr& src) {
auto llc_payload_len = llc_frame_len - sizeof(LlcHeader);
finspect("Inbound LLC frame: len %zu\n", llc_frame_len);
finspect(" llc hdr: %s\n", debug::Describe(llc_frame).c_str());
auto payload = reinterpret_cast<const uint8_t*>(&llc_frame) + sizeof(LlcHeader);
finspect(" llc payload: %s\n", debug::HexDump(payload, llc_payload_len).c_str());
// Prepare a packet
const size_t eth_len = llc_payload_len + sizeof(EthernetII);
auto buffer = GetBuffer(eth_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto eth_packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), eth_len));
eth_packet->set_peer(Packet::Peer::kEthernet);
// Construct an Ethernet frame
auto eth = eth_packet->mut_field<EthernetII>(0);
eth->dest = dest;
eth->src = src;
eth->ether_type = llc_frame.protocol_id;
std::memcpy(eth->payload, llc_frame.payload, llc_payload_len);
// Send up
zx_status_t status = device_->SendEthernet(std::move(eth_packet));
if (status != ZX_OK) { errorf("could not send ethernet data: %d\n", status); }
return status;
}
zx_status_t Station::HandleAmsduFrame(const DataFrame<LlcHeader>& frame) {
// TODO(porce): Define A-MSDU or MSDU signature, and avoid forceful conversion.
debugfn();
ZX_DEBUG_ASSERT(frame.hdr()->fc.subtype() >= DataSubtype::kQosdata);
ZX_DEBUG_ASSERT(frame.hdr()->fc.subtype() <= DataSubtype::kQosdataCfackCfpoll);
// Non-DMG stations use basic subframe format only.
auto amsdu_len = frame.body_len();
if (amsdu_len == 0) { return ZX_OK; }
// TODO(porce): The received AMSDU should not be greater than max_amsdu_len, specified in
// HtCapabilities IE of Association. Warn or discard if violated.
if (amsdu_len < sizeof(AmsduSubframeHeader)) {
errorf("dropping malformed A-MSDU of len %zu\n", amsdu_len);
return ZX_ERR_STOP;
}
// LLC frame should be interpreted as A-MSDU
auto amsdu = reinterpret_cast<const uint8_t*>(frame.body());
finspect("Inbound AMSDU: len %zu\n", amsdu_len);
size_t offset = 0; // Tracks up to which point of byte stream is parsed.
while (offset < amsdu_len) {
size_t offset_prev = offset;
auto subframe = reinterpret_cast<const AmsduSubframe*>(amsdu + offset);
if (!(offset + sizeof(AmsduSubframeHeader) <= amsdu_len)) {
errorf(
"malformed A-MSDU subframe: amsdu_len %zu offset %zu offset_prev %zu "
"AmsduSubframeHeader size: %zu\n",
amsdu_len, offset, offset_prev, sizeof(AmsduSubframeHeader));
return ZX_ERR_STOP;
}
finspect("amsdu subframe: %s\n", debug::Describe(*subframe).c_str());
finspect(
"amsdu subframe dump: %s\n",
debug::HexDump(reinterpret_cast<const uint8_t*>(subframe), sizeof(AmsduSubframeHeader))
.c_str());
offset += sizeof(AmsduSubframeHeader);
auto msdu_len = subframe->hdr.msdu_len();
// Note: msdu_len == 0 is valid
if (msdu_len > 0) {
if (!(offset + msdu_len <= amsdu_len && msdu_len >= sizeof(LlcHeader))) {
errorf("malformed A-MSDU subframe: amsdu_len %zu offset %zu msdu_len %u\n",
amsdu_len, offset, msdu_len);
return ZX_ERR_STOP;
}
offset += msdu_len;
// TODO(porce): Process MSDU - Construct an Ethernet frame and send up
auto llc_frame = subframe->get_msdu();
HandleLlcFrame(*llc_frame, msdu_len, subframe->hdr.da, subframe->hdr.sa);
}
// Skip by zero-padding
bool is_last_subframe = (offset == amsdu_len);
offset += subframe->PaddingLen(is_last_subframe);
ZX_DEBUG_ASSERT(offset > offset_prev); // Otherwise infinite loop
}
return ZX_OK;
}
zx_status_t Station::HandleEthFrame(const EthFrame& frame) {
debugfn();
// Drop Ethernet frames when not associated.
auto bss_setup = (bssid() != nullptr);
auto associated = (state_ == WlanState::kAssociated);
if (!associated) { debugf("dropping eth packet while not associated\n"); }
if (!bss_setup || !associated) { return ZX_OK; }
auto eth = frame.hdr();
const size_t buf_len = kDataFrameHdrLenMax + sizeof(LlcHeader) + frame.body_len();
auto buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
wlan_tx_info_t txinfo = {
// TODO(porce): Determine PHY and CBW based on the association negotiation.
.tx_flags = 0x0,
.valid_fields =
WLAN_TX_INFO_VALID_PHY | WLAN_TX_INFO_VALID_CHAN_WIDTH | WLAN_TX_INFO_VALID_MCS,
.phy = WLAN_PHY_HT,
.cbw = CBW20,
//.date_rate = 0x0,
.mcs = 0x7,
};
auto wlan_packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
// no need to clear the whole packet; we memset the headers instead and copy over all bytes in
// the payload
wlan_packet->set_peer(Packet::Peer::kWlan);
auto hdr = wlan_packet->mut_field<DataFrameHeader>(0);
bool has_qos_ctrl = IsQosReady();
bool has_ht_ctrl = false;
// Set header
std::memset(hdr, 0, kDataFrameHdrLenMax);
hdr->fc.set_type(FrameType::kData);
hdr->fc.set_subtype(has_qos_ctrl ? DataSubtype::kQosdata : DataSubtype::kDataSubtype);
hdr->fc.set_to_ds(1);
hdr->fc.set_from_ds(0);
hdr->fc.set_htc_order(has_ht_ctrl ? 1 : 0);
// Ensure all outgoing data frames are protected when RSNA is established.
if (!bss_->rsn.is_null() && controlled_port_ == eapol::PortState::kOpen) {
hdr->fc.set_protected_frame(1);
txinfo.tx_flags |= WLAN_TX_INFO_FLAGS_PROTECTED;
}
hdr->addr1 = common::MacAddr(bss_->bssid.data());
hdr->addr2 = eth->src;
hdr->addr3 = eth->dest;
// TODO(porce): Construct addr4 field
if (IsCbw40TxReady()) {
// Ralink appears to setup BlockAck session AND AMPDU handling
// TODO(porce): Use a separate sequence number space in that case
if (hdr->addr3.IsUcast()) {
// 40MHz direction does not matter here.
// Radio uses the operational channel setting. This indicates the bandwidth without
// direction.
txinfo.cbw = CBW40;
}
}
if (hdr->HasQosCtrl()) { // QoS Control field
auto qos_ctrl = hdr->qos_ctrl();
qos_ctrl->set_tid(GetTid(frame));
qos_ctrl->set_eosp(0);
qos_ctrl->set_ack_policy(ack_policy::kNormalAck);
// AMSDU: set_amsdu_present(1) requires dot11HighthroughputOptionImplemented should be true.
qos_ctrl->set_amsdu_present(0);
qos_ctrl->set_byte(0);
}
// TODO(porce): Construct htc_order field
SetSeqNo(hdr, &seq_);
auto llc = wlan_packet->mut_field<LlcHeader>(hdr->len());
llc->dsap = kLlcSnapExtension;
llc->ssap = kLlcSnapExtension;
llc->control = kLlcUnnumberedInformation;
std::memcpy(llc->oui, kLlcOui, sizeof(llc->oui));
llc->protocol_id = eth->ether_type;
std::memcpy(llc->payload, eth->payload, frame.body_len());
auto frame_len = hdr->len() + sizeof(LlcHeader) + frame.body_len();
auto status = wlan_packet->set_len(frame_len);
if (status != ZX_OK) {
errorf("could not set data frame length to %zu: %d\n", frame_len, status);
return status;
}
finspect("Outbound data frame: len %zu, hdr_len:%u body_len:%zu frame_len:%zu\n",
wlan_packet->len(), hdr->len(), frame.body_len(), frame_len);
finspect(" wlan hdr: %s\n", debug::Describe(*hdr).c_str());
finspect(" llc hdr: %s\n", debug::Describe(*llc).c_str());
finspect(" frame : %s\n", debug::HexDump(wlan_packet->data(), frame_len).c_str());
wlan_packet->CopyCtrlFrom(txinfo);
status = device_->SendWlan(std::move(wlan_packet));
if (status != ZX_OK) { errorf("could not send wlan data: %d\n", status); }
return status;
}
zx_status_t Station::HandleTimeout() {
debugfn();
zx::time now = timer_->Now();
if (join_timeout_ > zx::time() && now > join_timeout_) {
debugjoin("join timed out; resetting\n");
Reset();
return service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::JOIN_FAILURE_TIMEOUT);
}
if (auth_timeout_ > zx::time() && now >= auth_timeout_) {
debugjoin("auth timed out; moving back to joining\n");
auth_timeout_ = zx::time();
return service::SendAuthConfirm(device_, bssid_,
wlan_mlme::AuthenticateResultCodes::AUTH_FAILURE_TIMEOUT);
}
if (assoc_timeout_ > zx::time() && now >= assoc_timeout_) {
debugjoin("assoc timed out; moving back to authenticated\n");
assoc_timeout_ = zx::time();
// TODO(tkilbourn): need a better error code for this
return service::SendAssocConfirm(device_,
wlan_mlme::AssociateResultCodes::REFUSED_TEMPORARILY);
}
if (signal_report_timeout_ > zx::time() && now > signal_report_timeout_ &&
state_ == WlanState::kAssociated) {
signal_report_timeout_ = deadline_after_bcn_period(kSignalReportBcnCountTimeout);
timer_->SetTimer(signal_report_timeout_);
service::SendSignalReportIndication(device_, common::to_dBm(avg_rssi_dbm_.avg()));
}
return ZX_OK;
}
zx_status_t Station::SendKeepAliveResponse() {
if (state_ != WlanState::kAssociated) {
warnf("cannot send keep alive response before being associated\n");
return ZX_OK;
}
const common::MacAddr& mymac = device_->GetState()->address();
size_t len = sizeof(DataFrameHeader);
fbl::unique_ptr<Buffer> buffer = GetBuffer(len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<DataFrameHeader>(0);
hdr->fc.set_type(FrameType::kData);
hdr->fc.set_subtype(DataSubtype::kNull);
hdr->fc.set_to_ds(1);
common::MacAddr bssid(bss_->bssid.data());
hdr->addr1 = bssid;
hdr->addr2 = mymac;
hdr->addr3 = bssid;
SetSeqNo(hdr, &seq_);
zx_status_t status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send keep alive packet: %d\n", status);
return status;
}
return ZX_OK;
}
zx_status_t Station::SendAddBaRequestFrame() {
debugfn();
if (state_ != WlanState::kAssociated) {
errorf("won't send ADDBA Request in other than Associated state. Current state: %d\n",
state_);
return ZX_ERR_BAD_STATE;
}
MgmtFrame<AddBaRequestFrame> frame;
auto status = BuildMgmtFrame(&frame);
if (status != ZX_OK) { return status; }
auto hdr = frame.hdr();
auto req = frame.body();
const common::MacAddr& mymac = device_->GetState()->address();
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
SetSeqNo(hdr, &seq_);
frame.FillTxInfo();
req->category = action::Category::kBlockAck;
req->action = action::BaAction::kAddBaRequest;
// It appears there is no particular rule to choose the value for
// dialog_token. See IEEE Std 802.11-2016, 9.6.5.2.
req->dialog_token = 0x01;
req->params.set_amsdu(IsAmsduRxReady());
req->params.set_policy(BlockAckParameters::BlockAckPolicy::kImmediate);
req->params.set_tid(GetTid()); // TODO(porce): Communicate this with lower MAC.
// TODO(porce): Fix the discrepancy of this value from the Ralink's TXWI ba_win_size setting
req->params.set_buffer_size(64);
req->timeout = 0; // Disables the timeout
req->seq_ctrl.set_fragment(0); // TODO(porce): Send this down to the lower MAC
req->seq_ctrl.set_starting_seq(1);
finspect("Outbound ADDBA Req frame: len %zu\n", frame.len());
finspect(" addba req: %s\n", debug::Describe(*req).c_str());
finspect("Outbound Mgmt Frame(ADDBA Req): %s\n", debug::Describe(*hdr).c_str());
status = device_->SendWlan(frame.Take());
if (status != ZX_OK) {
errorf("could not send AddBaRequest: %d\n", status);
return status;
}
return ZX_OK;
}
zx_status_t Station::HandleMlmeEapolReq(const MlmeMsg<wlan_mlme::EapolRequest>& req) {
debugfn();
if (!bss_) { return ZX_ERR_BAD_STATE; }
if (state_ != WlanState::kAssociated) {
debugf("dropping MLME-EAPOL.request while not being associated. STA in state %d\n", state_);
return ZX_OK;
}
size_t len = sizeof(DataFrameHeader) + sizeof(LlcHeader) + req.body()->data->size();
fbl::unique_ptr<Buffer> buffer = GetBuffer(len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<DataFrameHeader>(0);
hdr->fc.set_type(FrameType::kData);
hdr->fc.set_to_ds(1);
hdr->addr1.Set(req.body()->dst_addr.data());
hdr->addr2.Set(req.body()->src_addr.data());
hdr->addr3.Set(req.body()->dst_addr.data());
SetSeqNo(hdr, &seq_);
auto llc = packet->mut_field<LlcHeader>(sizeof(DataFrameHeader));
llc->dsap = kLlcSnapExtension;
llc->ssap = kLlcSnapExtension;
llc->control = kLlcUnnumberedInformation;
std::memcpy(llc->oui, kLlcOui, sizeof(llc->oui));
llc->protocol_id = htobe16(kEapolProtocolId);
std::memcpy(llc->payload, req.body()->data->data(), req.body()->data->size());
zx_status_t status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send eapol request packet: %d\n", status);
service::SendEapolConfirm(device_, wlan_mlme::EapolResultCodes::TRANSMISSION_FAILURE);
return status;
}
service::SendEapolConfirm(device_, wlan_mlme::EapolResultCodes::SUCCESS);
return status;
}
zx_status_t Station::HandleMlmeSetKeysReq(const MlmeMsg<wlan_mlme::SetKeysRequest>& req) {
debugfn();
for (auto& keyDesc : *req.body()->keylist) {
if (keyDesc.key.is_null()) { return ZX_ERR_NOT_SUPPORTED; }
uint8_t key_type;
switch (keyDesc.key_type) {
case wlan_mlme::KeyType::PAIRWISE:
key_type = WLAN_KEY_TYPE_PAIRWISE;
break;
case wlan_mlme::KeyType::PEER_KEY:
key_type = WLAN_KEY_TYPE_PEER;
break;
case wlan_mlme::KeyType::IGTK:
key_type = WLAN_KEY_TYPE_IGTK;
break;
default:
key_type = WLAN_KEY_TYPE_GROUP;
break;
}
wlan_key_config_t key_config = {};
memcpy(key_config.key, keyDesc.key->data(), keyDesc.length);
key_config.key_type = key_type;
key_config.key_len = static_cast<uint8_t>(keyDesc.length);
key_config.key_idx = keyDesc.key_id;
key_config.protection = WLAN_PROTECTION_RX_TX;
key_config.cipher_type = keyDesc.cipher_suite_type;
memcpy(key_config.cipher_oui, keyDesc.cipher_suite_oui.data(),
sizeof(key_config.cipher_oui));
memcpy(key_config.peer_addr, keyDesc.address.data(), sizeof(key_config.peer_addr));
auto status = device_->SetKey(&key_config);
if (status != ZX_OK) {
errorf("Could not configure keys in hardware: %d\n", status);
return status;
}
}
// Once keys have been successfully configured, open controlled port and report link up
// status.
// TODO(hahnr): This is a very simplified assumption and we might need a little more logic to
// correctly track the port's state.
controlled_port_ = eapol::PortState::kOpen;
device_->SetStatus(ETH_STATUS_ONLINE);
return ZX_OK;
}
zx_status_t Station::PreChannelChange(wlan_channel_t chan) {
debugfn();
if (state_ != WlanState::kAssociated) { return ZX_OK; }
if (GetJoinChan().primary == GetDeviceChan().primary) {
SetPowerManagementMode(true);
// TODO(hahnr): start buffering tx packets (not here though)
}
return ZX_OK;
}
zx_status_t Station::PostChannelChange() {
debugfn();
if (state_ != WlanState::kAssociated) { return ZX_OK; }
if (GetJoinChan().primary == GetDeviceChan().primary) {
SetPowerManagementMode(false);
// TODO(hahnr): wait for TIM, and PS-POLL all buffered frames from AP.
}
return ZX_OK;
}
void Station::DumpDataFrame(const DataFrame<LlcHeader>& frame) {
// TODO(porce): Should change the API signature to MSDU
const common::MacAddr& mymac = device_->GetState()->address();
auto hdr = frame.hdr();
auto is_ucast_to_self = mymac == hdr->addr1;
auto is_mcast = hdr->addr1.IsBcast();
auto is_bcast = hdr->addr1.IsMcast();
auto is_interesting = is_ucast_to_self || is_mcast || is_bcast;
auto associated = (state_ == WlanState::kAssociated);
auto from_bss = (bssid() != nullptr && *bssid() == hdr->addr2);
if (associated) { is_interesting = is_interesting && from_bss; }
if (!is_interesting) { return; }
auto msdu = reinterpret_cast<const uint8_t*>(frame.body());
finspect("Inbound data frame: len %zu\n", frame.len());
finspect(" wlan hdr: %s\n", debug::Describe(*hdr).c_str());
finspect(" msdu : %s\n", debug::HexDump(msdu, frame.body_len()).c_str());
}
zx_status_t Station::SetPowerManagementMode(bool ps_mode) {
if (state_ != WlanState::kAssociated) {
warnf("cannot adjust power management before being associated\n");
return ZX_OK;
}
const common::MacAddr& mymac = device_->GetState()->address();
size_t len = sizeof(DataFrameHeader);
fbl::unique_ptr<Buffer> buffer = GetBuffer(len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<DataFrameHeader>(0);
hdr->fc.set_type(FrameType::kData);
hdr->fc.set_subtype(DataSubtype::kNull);
hdr->fc.set_pwr_mgmt(ps_mode);
hdr->fc.set_to_ds(1);
common::MacAddr bssid(bss_->bssid.data());
hdr->addr1 = bssid;
hdr->addr2 = mymac;
hdr->addr3 = bssid;
SetSeqNo(hdr, &seq_);
zx_status_t status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send power management packet: %d\n", status);
return status;
}
return ZX_OK;
}
zx_status_t Station::SendPsPoll() {
// TODO(hahnr): We should probably wait for an RSNA if the network is an
// RSN. Else we cannot work with the incoming data frame.
if (state_ != WlanState::kAssociated) {
warnf("cannot send ps-poll before being associated\n");
return ZX_OK;
}
const common::MacAddr& mymac = device_->GetState()->address();
size_t len = sizeof(PsPollFrame);
fbl::unique_ptr<Buffer> buffer = GetBuffer(len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto frame = packet->mut_field<PsPollFrame>(0);
frame->fc.set_type(FrameType::kControl);
frame->fc.set_subtype(ControlSubtype::kPsPoll);
frame->aid = aid_;
frame->bssid = common::MacAddr(bss_->bssid.data());
frame->ta = mymac;
zx_status_t status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send power management packet: %d\n", status);
return status;
}
return ZX_OK;
}
zx::time Station::deadline_after_bcn_period(size_t bcn_count) {
ZX_DEBUG_ASSERT(bss_ != nullptr);
return timer_->Now() + WLAN_TU(bss_->beacon_period * bcn_count);
}
bool Station::IsHTReady() const {
// TODO(porce): Test capabilites and configurations of the client and its BSS.
return true;
}
bool Station::IsCbw40RxReady() const {
// TODO(porce): Test capabilites and configurations of the client and its BSS.
return true;
}
bool Station::IsCbw40TxReady() const {
// TODO(porce): Test capabilites and configurations of the client and its BSS.
// TODO(porce): Ralink dependency on BlockAck, AMPDU handling
return false;
}
bool Station::IsQosReady() const {
// TODO(NET-567,NET-599): Determine for each outbound data frame,
// given the result of the dynamic capability negotiation, data frame
// classification, and QoS policy.
// Aruba / Ubiquiti are confirmed to be compatible with QoS field for the BlockAck session,
// independently of 40MHz operation.
return true;
}
bool Station::IsAmsduRxReady() const {
// [Interop]
// IEEE Std 802.11-2016 9.4.1.14's wording is ambiguous, and it can cause interop issue.
// In particular, a peer may tear off BlockAck session if interpretation of the field
// "A-MSDU Supported" in Block Ack Parameter set of ADDBA Request and Response is different.
// Declare such that Fuchsia "can do" AMSDU. This hints the peer that
// peer may assume that this Fuchsia device can process inbound A-MSDU data frame.
// Since the presence of A-MSDU frame is indicated in the "amsdu_present" field of
// QoS field in MPDU header, and the use of A-MSDU frame is optional in flight-time,
// setting "A-MSDU Supported" both in ADDBA Request and Response is deemed to be most
// interoperable way.
return true;
}
HtCapabilities Station::BuildHtCapabilities() const {
// TODO(porce): Find intersection of
// - BSS capabilities
// - Client radio capabilities
// - Client configuration
// Static cooking for Proof-of-Concept
HtCapabilities htc;
HtCapabilityInfo& hci = htc.ht_cap_info;
hci.set_ldpc_coding_cap(0); // Ralink RT5370 is incapable of LDPC.
if (IsCbw40RxReady()) {
hci.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
} else {
hci.set_chan_width_set(HtCapabilityInfo::TWENTY_ONLY);
}
hci.set_sm_power_save(HtCapabilityInfo::DISABLED);
hci.set_greenfield(0);
hci.set_short_gi_20(1);
hci.set_short_gi_40(1);
hci.set_tx_stbc(0); // No plan to support STBC Tx
hci.set_rx_stbc(1); // one stream.
hci.set_delayed_block_ack(0);
// TODO(NET-599): Reflect the chipset capability and USB read size in negotiation
hci.set_max_amsdu_len(HtCapabilityInfo::OCTETS_3839);
hci.set_dsss_in_40(0);
hci.set_intolerant_40(0);
hci.set_lsig_txop_protect(0);
AmpduParams& ampdu = htc.ampdu_params;
ampdu.set_exponent(3); // 65535 bytes
ampdu.set_min_start_spacing(AmpduParams::FOUR_USEC); // Aruba
// ampdu.set_min_start_spacing(AmpduParams::EIGHT_USEC); // TP-Link
// ampdu.set_min_start_spacing(AmpduParams::SIXTEEN_USEC);
SupportedMcsSet& mcs = htc.mcs_set;
mcs.rx_mcs_head.set_bitmask(0xff); // MCS 0-7
// mcs.rx_mcs_head.set_bitmask(0xffff); // MCS 0-15
mcs.tx_mcs.set_set_defined(1); // Aruba
HtExtCapabilities& hec = htc.ht_ext_cap;
hec.set_pco(0);
hec.set_pco_transition(HtExtCapabilities::PCO_RESERVED);
hec.set_mcs_feedback(HtExtCapabilities::MCS_NOFEEDBACK);
hec.set_htc_ht_support(0);
hec.set_rd_responder(0);
TxBfCapability& txbf = htc.txbf_cap;
txbf.set_implicit_rx(0);
txbf.set_rx_stag_sounding(0);
txbf.set_tx_stag_sounding(0);
txbf.set_rx_ndp(0);
txbf.set_tx_ndp(0);
txbf.set_implicit(0);
txbf.set_calibration(TxBfCapability::CALIBRATION_NONE);
txbf.set_csi(0);
txbf.set_noncomp_steering(0);
txbf.set_comp_steering(0);
txbf.set_csi_feedback(TxBfCapability::FEEDBACK_NONE);
txbf.set_noncomp_feedback(TxBfCapability::FEEDBACK_NONE);
txbf.set_comp_feedback(TxBfCapability::FEEDBACK_NONE);
txbf.set_min_grouping(TxBfCapability::MIN_GROUP_ONE);
txbf.set_csi_antennas_human(1); // 1 antenna
txbf.set_noncomp_steering_ants_human(1); // 1 antenna
txbf.set_comp_steering_ants_human(1); // 1 antenna
txbf.set_csi_rows_human(1); // 1 antenna
txbf.set_chan_estimation_human(1); // # space-time stream
AselCapability& asel = htc.asel_cap;
asel.set_asel(0);
asel.set_csi_feedback_tx_asel(0);
asel.set_explicit_csi_feedback(0);
asel.set_antenna_idx_feedback(0);
asel.set_rx_asel(0);
asel.set_tx_sounding_ppdu(0);
return htc; // 28 bytes.
}
uint8_t Station::GetTid() {
// IEEE Std 802.11-2016, 3.1(Traffic Identifier), 5.1.1.1 (Data Service - General), 9.4.2.30
// (Access Policy), 9.2.4.5.2 (TID subfield) Related topics: QoS facility, TSPEC, WM, QMF, TXOP.
// A TID is from [0, 15], and is assigned to an MSDU in the layers above the MAC.
// [0, 7] identify Traffic Categories (TCs)
// [8, 15] identify parameterized Traffic Streams (TSs).
// TODO(NET-599): Implement QoS policy engine.
return 0;
}
uint8_t Station::GetTid(const EthFrame& frame) {
return GetTid();
}
wlan_stats::ClientMlmeStats Station::stats() const {
return stats_.ToFidl();
}
} // namespace wlan