blob: 76c7292c6ed445739c029e16ee77738cb4df44ca [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 "station.h"
#include "device_interface.h"
#include "logging.h"
#include "mac_frame.h"
#include "packet.h"
#include "serialize.h"
#include "timer.h"
#include <cstring>
#include <utility>
namespace wlan {
// TODO(hahnr): Revisit frame construction to reduce boilerplate code.
static constexpr zx_duration_t kAssocTimeoutTu = 20;
static constexpr zx_duration_t kSignalReportTimeoutTu = 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_ = 0;
auth_timeout_ = 0;
last_seen_ = 0;
bssid_.Reset();
}
bool Station::ShouldDropMlmeMessage(Method& method) {
// Always allow MLME-JOIN.request.
if (method == Method::JOIN_request) { return false; }
// Drop other MLME requests if there is no BSSID set yet.
return bssid() == nullptr;
}
zx_status_t Station::HandleMlmeJoinReq(const JoinRequest& req) {
debugfn();
if (req.selected_bss.is_null()) {
errorf("bad join request\n");
// Don't reset because of a bad request. Just send the response.
return SendJoinResponse();
}
if (state_ != WlanState::kUnjoined) {
warnf("already joined; resetting station\n");
Reset();
}
// Clone request to take ownership of the BSS>
auto req_clone = req.Clone();
bss_ = std::move(req_clone->selected_bss);
bssid_.Set(bss_->bssid.data());
debugjoin("setting channel to %u\n", bss_->channel);
zx_status_t status = device_->SetChannel(wlan_channel_t{bss_->channel});
if (status != ZX_OK) {
errorf("could not set wlan channel: %d\n", status);
Reset();
SendJoinResponse();
return status;
}
join_timeout_ = deadline_after_bcn_period(req.join_failure_timeout);
status = timer_->SetTimer(join_timeout_);
if (status != ZX_OK) {
errorf("could not set join timer: %d\n", status);
Reset();
SendJoinResponse();
}
// TODO(hahnr): Update when other BSS types are supported.
device_->SetBss(bssid_, WLAN_BSS_TYPE_INFRASTRUCTURE);
return status;
}
zx_status_t Station::HandleMlmeAuthReq(const AuthenticateRequest& req) {
debugfn();
if (bss_.is_null()) { return ZX_ERR_BAD_STATE; }
// TODO(tkilbourn): better result codes
if (!bss_->bssid.Equals(req.peer_sta_address)) {
errorf("cannot authenticate before joining\n");
return SendAuthResponse(AuthenticateResultCodes::REFUSED);
}
if (state_ == WlanState::kUnjoined) {
errorf("must join before authenticating\n");
return SendAuthResponse(AuthenticateResultCodes::REFUSED);
}
if (state_ != WlanState::kUnauthenticated) {
warnf("already authenticated; sending request anyway\n");
}
if (req.auth_type != 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 SendAuthResponse(AuthenticateResultCodes::REFUSED);
}
debugjoin("authenticating to %s\n", MACSTR(bssid_));
// TODO(tkilbourn): better size management
size_t auth_len = sizeof(MgmtFrameHeader) + sizeof(Authentication);
fbl::unique_ptr<Buffer> buffer = GetBuffer(auth_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
const common::MacAddr& mymac = device_->GetState()->address();
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), auth_len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<MgmtFrameHeader>(0);
hdr->fc.set_type(kManagement);
hdr->fc.set_subtype(kAuthentication);
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
hdr->sc.set_seq(next_seq());
auto auth = packet->mut_field<Authentication>(sizeof(MgmtFrameHeader));
// TODO(tkilbourn): this assumes Open System authentication
auth->auth_algorithm_number = auth_alg_;
auth->auth_txn_seq_number = 1;
auth->status_code = 0; // Reserved, so set to 0
zx_status_t status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send auth packet: %d\n", status);
SendAuthResponse(AuthenticateResultCodes::REFUSED);
return status;
}
auth_timeout_ = deadline_after_bcn_period(req.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.
SendAuthResponse(AuthenticateResultCodes::AUTH_FAILURE_TIMEOUT);
// TODO(tkilbourn): reset the station?
}
return status;
}
zx_status_t Station::HandleMlmeDeauthReq(const DeauthenticateRequest& req) {
debugfn();
ZX_DEBUG_ASSERT(!req.peer_sta_address.is_null());
if (state_ != WlanState::kAssociated && state_ != WlanState::kAuthenticated) {
errorf("not associated or authenticated; ignoring deauthenticate request\n");
return ZX_OK;
}
if (bss_.is_null()) { return ZX_ERR_BAD_STATE; }
// Check whether the request wants to deauthenticate from this STA's BSS.
common::MacAddr peer_sta_addr(req.peer_sta_address.data());
if (bssid_ != peer_sta_addr) { return ZX_OK; }
size_t deauth_len = sizeof(MgmtFrameHeader) + sizeof(Deauthentication);
fbl::unique_ptr<Buffer> buffer = GetBuffer(deauth_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), deauth_len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<MgmtFrameHeader>(0);
hdr->fc.set_type(kManagement);
hdr->fc.set_subtype(kDeauthentication);
const common::MacAddr& mymac = device_->GetState()->address();
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
hdr->sc.set_seq(next_seq());
auto deauth = packet->mut_field<Deauthentication>(sizeof(MgmtFrameHeader));
deauth->reason_code = req.reason_code;
zx_status_t status = device_->SendWlan(std::move(packet));
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=%u\n", bss_->ssid.data(), req.reason_code);
// TODO(hahnr): Refactor once we have the new state machine.
state_ = WlanState::kUnauthenticated;
device_->SetStatus(0);
controlled_port_ = PortState::kBlocked;
SendDeauthResponse(peer_sta_addr);
return ZX_OK;
}
zx_status_t Station::HandleMlmeAssocReq(const AssociateRequest& req) {
debugfn();
if (bss_.is_null()) { return ZX_ERR_BAD_STATE; }
// TODO(tkilbourn): better result codes
if (!bss_->bssid.Equals(req.peer_sta_address)) {
errorf("bad peer STA address for association\n");
return SendAuthResponse(AuthenticateResultCodes::REFUSED);
}
if (state_ == WlanState::kUnjoined || state_ == WlanState::kUnauthenticated) {
errorf("must authenticate before associating\n");
return SendAuthResponse(AuthenticateResultCodes::REFUSED);
}
if (state_ == WlanState::kAssociated) {
warnf("already authenticated; sending request anyway\n");
}
debugjoin("associating to %s\n", MACSTR(bssid_));
// TODO(tkilbourn): better size management; for now reserve 128 bytes for Association elements
size_t assoc_len = sizeof(MgmtFrameHeader) + sizeof(AssociationRequest) + 128;
fbl::unique_ptr<Buffer> buffer = GetBuffer(assoc_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
const common::MacAddr& mymac = device_->GetState()->address();
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), assoc_len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<MgmtFrameHeader>(0);
hdr->fc.set_type(kManagement);
hdr->fc.set_subtype(kAssociationRequest);
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
hdr->sc.set_seq(next_seq());
// TODO(tkilbourn): a lot of this is hardcoded for now. Use device capabilities to set up the
// request.
auto assoc = packet->mut_field<AssociationRequest>(sizeof(MgmtFrameHeader));
assoc->cap.set_ess(1);
assoc->cap.set_short_preamble(1);
assoc->listen_interval = 0;
ElementWriter w(assoc->elements,
packet->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());
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
// TODO(tkilbourn): add extended rates support to get the rest of 802.11g rates.
// 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");
SendAssocResponse(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");
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
// Write RSNE from MLME-Association.request if available.
if (req.rsn) {
if (!w.write<RsnElement>(req.rsn.data(), req.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");
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_IO;
}
}
// Validate the request in debug mode
ZX_DEBUG_ASSERT(assoc->Validate(w.size()));
size_t actual_len = sizeof(MgmtFrameHeader) + sizeof(AssociationRequest) + w.size();
zx_status_t status = packet->set_len(actual_len);
if (status != ZX_OK) {
errorf("could not set packet length to %zu: %d\n", actual_len, status);
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return status;
}
status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send assoc packet: %d\n", status);
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return status;
}
// TODO(tkilbourn): get the assoc timeout from somewhere
assoc_timeout_ = deadline_after_bcn_period(kAssocTimeoutTu);
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.
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
// TODO(tkilbourn): reset the station?
}
return status;
}
bool Station::ShouldDropMgmtFrame(const MgmtFrameHeader& hdr) {
// Drop management frames if either, there is no BSSID set yet,
// or the frame is not from the BSS.
return bssid() == nullptr || *bssid() != hdr.addr3;
}
// TODO(hahnr): Support ProbeResponses.
zx_status_t Station::HandleBeacon(const MgmtFrame<Beacon>& frame, const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(!bss_.is_null());
ZX_DEBUG_ASSERT(frame.hdr->fc.subtype() == ManagementSubtype::kBeacon);
ZX_DEBUG_ASSERT(frame.hdr->addr3 == common::MacAddr(bss_->bssid.data()));
avg_rssi_.add(rxinfo.rssi);
// TODO(tkilbourn): update any other info (like rolling average of rssi)
last_seen_ = timer_->Now();
if (join_timeout_ > 0) {
join_timeout_ = 0;
timer_->CancelTimer();
state_ = WlanState::kUnauthenticated;
debugjoin("joined %s\n", bss_->ssid.data());
return SendJoinResponse();
}
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;
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,
const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(frame.hdr->fc.subtype() == ManagementSubtype::kAuthentication);
ZX_DEBUG_ASSERT(frame.hdr->addr3 == common::MacAddr(bss_->bssid.data()));
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?
SendAuthResponse(AuthenticateResultCodes::AUTHENTICATION_REJECTED);
return ZX_ERR_BAD_STATE;
}
common::MacAddr bssid(bss_->bssid.data());
debugjoin("authenticated to %s\n", MACSTR(bssid));
state_ = WlanState::kAuthenticated;
auth_timeout_ = 0;
timer_->CancelTimer();
SendAuthResponse(AuthenticateResultCodes::SUCCESS);
return ZX_OK;
}
zx_status_t Station::HandleDeauthentication(const MgmtFrame<Deauthentication>& frame,
const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(frame.hdr->fc.subtype() == ManagementSubtype::kDeauthentication);
ZX_DEBUG_ASSERT(frame.hdr->addr3 == common::MacAddr(bss_->bssid.data()));
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=%u\n", bss_->ssid.data(), deauth->reason_code);
state_ = WlanState::kUnauthenticated;
device_->SetStatus(0);
controlled_port_ = PortState::kBlocked;
return SendDeauthIndication(deauth->reason_code);
}
zx_status_t Station::HandleAssociationResponse(const MgmtFrame<AssociationResponse>& frame,
const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(frame.hdr->fc.subtype() == ManagementSubtype::kAssociationResponse);
ZX_DEBUG_ASSERT(frame.hdr->addr3 == common::MacAddr(bss_->bssid.data()));
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
SendAssocResponse(AssociateResultCodes::REFUSED_REASON_UNSPECIFIED);
return ZX_ERR_BAD_STATE;
}
common::MacAddr bssid(bss_->bssid.data());
debugjoin("associated with %s\n", MACSTR(bssid));
state_ = WlanState::kAssociated;
assoc_timeout_ = 0;
aid_ = assoc->aid & kAidMask;
timer_->CancelTimer();
SendAssocResponse(AssociateResultCodes::SUCCESS);
signal_report_timeout_ = deadline_after_bcn_period(kSignalReportTimeoutTu);
timer_->SetTimer(signal_report_timeout_);
avg_rssi_.reset();
avg_rssi_.add(rxinfo.rssi);
SendSignalReportIndication(rxinfo.rssi);
// 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_ = PortState::kOpen;
device_->SetStatus(ETH_STATUS_ONLINE);
}
std::printf("associated\n");
return ZX_OK;
}
zx_status_t Station::HandleDisassociation(const MgmtFrame<Disassociation>& frame,
const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(frame.hdr->fc.subtype() == ManagementSubtype::kDisassociation);
ZX_DEBUG_ASSERT(frame.hdr->addr3 == common::MacAddr(bss_->bssid.data()));
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_ = PortState::kBlocked;
signal_report_timeout_ = 0;
timer_->CancelTimer();
return SendDisassociateIndication(disassoc->reason_code);
}
zx_status_t Station::HandleAddBaRequestFrame(const MgmtFrame<AddBaRequestFrame>& frame,
const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(frame.hdr->fc.subtype() == ManagementSubtype::kAction);
ZX_DEBUG_ASSERT(frame.hdr->addr3 == common::MacAddr(bss_->bssid.data()));
ZX_DEBUG_ASSERT(frame.body->category == action::Category::kBlockAck);
ZX_DEBUG_ASSERT(frame.body->action == action::BaAction::kAddBaRequest);
auto addbar = frame.body;
debugf(
"Rxed ADDBAR: token %3u, amsdu %u policy %u tid %u buffer_size %u, timeout %u, "
"fragment "
"%u starting_seq %u",
addbar->dialog_token, addbar->params.amsdu(), addbar->params.policy(), addbar->params.tid(),
addbar->params.buffer_size(), addbar->timeout, addbar->seq_ctrl.fragment(),
addbar->seq_ctrl.starting_seq());
// Construct AddBaResponse frame
// Here MgmtFrameHeader construction does not use an optional header field.
// TODO(porce): Build a robust frame header constructor.
size_t frame_len = sizeof(MgmtFrameHeader) + sizeof(AddBaResponseFrame);
fbl::unique_ptr<Buffer> buffer = GetBuffer(frame_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
const common::MacAddr& mymac = device_->GetState()->address();
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), frame_len));
packet->clear();
packet->set_peer(Packet::Peer::kWlan);
auto hdr = packet->mut_field<MgmtFrameHeader>(0);
hdr->fc.set_type(kManagement);
hdr->fc.set_subtype(kAction);
hdr->addr1 = bssid_;
hdr->addr2 = mymac;
hdr->addr3 = bssid_;
hdr->sc.set_seq(next_seq());
auto resp = packet->mut_field<AddBaResponseFrame>(sizeof(MgmtFrameHeader));
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.
resp->params.set_amsdu(0);
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;
zx_status_t status = device_->SendWlan(std::move(packet));
if (status != ZX_OK) {
errorf("could not send AddBaResponse: %d\n", status);
return status;
}
return ZX_OK;
}
bool Station::ShouldDropDataFrame(const DataFrameHeader& hdr) {
// Drop data frames if either, there is no BSSID set yet,
// or the frame is not from the BSS.
auto from_bss = (bssid() != nullptr && *bssid() == hdr.addr2);
auto associated = (state_ == WlanState::kAssociated);
if (!associated) { debugf("dropping data packet while not associated\n"); }
return !from_bss || !associated;
}
zx_status_t Station::HandleNullDataFrame(const DataFrameHeader& hdr, const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(hdr.fc.subtype() == DataSubtype::kNull);
ZX_DEBUG_ASSERT(bssid() != nullptr);
ZX_DEBUG_ASSERT(hdr.addr2 == common::MacAddr(bss_->bssid.data()));
ZX_DEBUG_ASSERT(state_ == WlanState::kAssociated);
// Take signal strength into account.
avg_rssi_.add(rxinfo.rssi);
// 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,
const wlan_rx_info_t& rxinfo) {
debugfn();
ZX_DEBUG_ASSERT(bssid() != nullptr);
ZX_DEBUG_ASSERT(frame.hdr->addr2 == common::MacAddr(bss_->bssid.data()));
ZX_DEBUG_ASSERT(state_ == WlanState::kAssociated);
switch (frame.hdr->fc.subtype()) {
case DataSubtype::kDataSubtype:
// Fall-through
case DataSubtype::kQosdata: // For data frames within BlockAck session.
break;
default:
warnf("unsupported data subtype %02x\n", frame.hdr->fc.subtype());
return ZX_OK;
}
// Take signal strength into account.
avg_rssi_.add(rxinfo.rssi);
auto hdr = frame.hdr;
auto llc = frame.body;
// Forward EAPOL frames to SME.
if (be16toh(llc->protocol_id) == kEapolProtocolId) {
if (frame.body_len < sizeof(EapolFrame)) {
warnf("short EAPOL frame; len = %zu", frame.body_len);
return ZX_OK;
}
auto eapol = reinterpret_cast<const EapolFrame*>(llc->payload);
uint16_t actual_body_len = frame.body_len;
uint16_t expected_body_len = be16toh(eapol->packet_body_length);
if (actual_body_len >= expected_body_len) {
return SendEapolIndication(eapol, hdr->addr3, hdr->addr1);
}
return ZX_OK;
}
// Drop packets if RSNA was not yet established.
if (controlled_port_ == PortState::kBlocked) { return ZX_OK; }
// PS-POLL if there are more buffered unicast frames.
if (hdr->fc.more_data() && hdr->addr1.IsUcast()) { SendPsPoll(); }
const size_t eth_len = frame.body_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));
// no need to clear the packet since every byte is overwritten
eth_packet->set_peer(Packet::Peer::kEthernet);
auto eth = eth_packet->mut_field<EthernetII>(0);
eth->dest = hdr->addr1;
eth->src = hdr->addr3;
eth->ether_type = llc->protocol_id;
std::memcpy(eth->payload, llc->payload, frame.body_len - sizeof(LlcHeader));
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;
}
bool Station::ShouldDropEthFrame(const BaseFrame<EthernetII>& hdr) {
// 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"); }
return !bss_setup || !associated;
}
zx_status_t Station::HandleEthFrame(const BaseFrame<EthernetII>& frame) {
debugfn();
ZX_DEBUG_ASSERT(bssid() != nullptr);
ZX_DEBUG_ASSERT(state_ == WlanState::kAssociated);
auto eth = frame.hdr;
const size_t wlan_len = kDataPayloadHeader + frame.body_len;
auto buffer = GetBuffer(wlan_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
wlan_tx_info_t txinfo = {};
auto wlan_packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), wlan_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);
std::memset(hdr, 0, sizeof(DataFrameHeader));
hdr->fc.set_type(kData);
hdr->fc.set_to_ds(1);
// Ensure all outgoing data frames are protected when RSNA is established.
if (!bss_->rsn.is_null() && controlled_port_ == 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;
hdr->sc.set_seq(next_seq());
debughdr("Frame control: %04x duration: %u seq: %u frag: %u\n", hdr->fc.val(), hdr->duration,
hdr->sc.seq(), hdr->sc.frag());
// TODO(porce): Fix this inconsistency. See how addr1,2,3 are assigned.
debughdr("dest: %s source: %s bssid: %s\n", MACSTR(hdr->addr1), MACSTR(hdr->addr2),
MACSTR(hdr->addr3));
auto llc = wlan_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 = eth->ether_type;
std::memcpy(llc->payload, eth->payload, frame.body_len);
wlan_packet->CopyCtrlFrom(txinfo);
zx_status_t 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_t now = timer_->Now();
if (join_timeout_ > 0 && now > join_timeout_) {
debugjoin("join timed out; resetting\n");
Reset();
return SendJoinResponse();
}
if (auth_timeout_ > 0 && now >= auth_timeout_) {
debugjoin("auth timed out; moving back to joining\n");
auth_timeout_ = 0;
return SendAuthResponse(AuthenticateResultCodes::AUTH_FAILURE_TIMEOUT);
}
if (assoc_timeout_ > 0 && now >= assoc_timeout_) {
debugjoin("assoc timed out; moving back to authenticated\n");
assoc_timeout_ = 0;
// TODO(tkilbourn): need a better error code for this
return SendAssocResponse(AssociateResultCodes::REFUSED_TEMPORARILY);
}
if (signal_report_timeout_ > 0 && now > signal_report_timeout_ &&
state_ == WlanState::kAssociated) {
signal_report_timeout_ = deadline_after_bcn_period(kSignalReportTimeoutTu);
timer_->SetTimer(signal_report_timeout_);
SendSignalReportIndication(avg_rssi_.avg());
}
return ZX_OK;
}
zx_status_t Station::SendJoinResponse() {
debugfn();
auto resp = JoinResponse::New();
resp->result_code = state_ == WlanState::kUnjoined ? JoinResultCodes::JOIN_FAILURE_TIMEOUT
: JoinResultCodes::SUCCESS;
size_t buf_len = sizeof(ServiceHeader) + resp->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::JOIN_confirm, resp);
if (status != ZX_OK) {
errorf("could not serialize JoinResponse: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::SendAuthResponse(AuthenticateResultCodes code) {
debugfn();
auto resp = AuthenticateResponse::New();
resp->peer_sta_address = fidl::Array<uint8_t>::New(common::kMacAddrLen);
common::MacAddr bssid(bss_->bssid.data());
bssid.CopyTo(resp->peer_sta_address.data());
// TODO(tkilbourn): set this based on the actual auth type
resp->auth_type = AuthenticationTypes::OPEN_SYSTEM;
resp->result_code = code;
size_t buf_len = sizeof(ServiceHeader) + resp->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::AUTHENTICATE_confirm, resp);
if (status != ZX_OK) {
errorf("could not serialize AuthenticateResponse: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::SendDeauthResponse(const common::MacAddr& peer_sta_addr) {
debugfn();
auto resp = DeauthenticateResponse::New();
resp->peer_sta_address = fidl::Array<uint8_t>::New(common::kMacAddrLen);
peer_sta_addr.CopyTo(resp->peer_sta_address.data());
size_t buf_len = sizeof(ServiceHeader) + resp->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::DEAUTHENTICATE_confirm, resp);
if (status != ZX_OK) {
errorf("could not serialize DeauthenticateResponse: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
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(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;
hdr->sc.set_seq(next_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::SendDeauthIndication(uint16_t code) {
debugfn();
auto ind = DeauthenticateIndication::New();
ind->peer_sta_address = fidl::Array<uint8_t>::New(common::kMacAddrLen);
common::MacAddr bssid(bss_->bssid.data());
bssid.CopyTo(ind->peer_sta_address.data());
ind->reason_code = code;
size_t buf_len = sizeof(ServiceHeader) + ind->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::DEAUTHENTICATE_indication, ind);
if (status != ZX_OK) {
errorf("could not serialize DeauthenticateIndication: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::SendAssocResponse(AssociateResultCodes code) {
debugfn();
auto resp = AssociateResponse::New();
resp->result_code = code;
resp->association_id = aid_;
size_t buf_len = sizeof(ServiceHeader) + resp->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::ASSOCIATE_confirm, resp);
if (status != ZX_OK) {
errorf("could not serialize AssociateResponse: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::SendDisassociateIndication(uint16_t code) {
debugfn();
auto ind = DisassociateIndication::New();
ind->peer_sta_address = fidl::Array<uint8_t>::New(common::kMacAddrLen);
common::MacAddr bssid(bss_->bssid.data());
bssid.CopyTo(ind->peer_sta_address.data());
ind->reason_code = code;
size_t buf_len = sizeof(ServiceHeader) + ind->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::DISASSOCIATE_indication, ind);
if (status != ZX_OK) {
errorf("could not serialize DisassociateIndication: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::SendSignalReportIndication(uint8_t rssi) {
debugfn();
if (state_ != WlanState::kAssociated) { return ZX_OK; }
auto ind = SignalReportIndication::New();
ind->rssi = rssi;
size_t buf_len = sizeof(ServiceHeader) + ind->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::SIGNAL_REPORT_indication, ind);
if (status != ZX_OK) {
errorf("could not serialize SignalReportIndication: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::HandleMlmeEapolReq(const 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.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(kData);
hdr->fc.set_to_ds(1);
hdr->addr1.Set(req.dst_addr.data());
hdr->addr2.Set(req.src_addr.data());
hdr->addr3.Set(req.dst_addr.data());
hdr->sc.set_seq(device_->GetState()->next_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.data.data(), req.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);
SendEapolResponse(EapolResultCodes::TRANSMISSION_FAILURE);
return status;
}
SendEapolResponse(EapolResultCodes::SUCCESS);
return status;
}
zx_status_t Station::SendEapolResponse(EapolResultCodes result_code) {
debugfn();
auto resp = EapolResponse::New();
resp->result_code = result_code;
size_t buf_len = sizeof(ServiceHeader) + resp->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::EAPOL_confirm, resp);
if (status != ZX_OK) {
errorf("could not serialize EapolResponse: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::SendEapolIndication(const EapolFrame* eapol, const common::MacAddr& src,
const common::MacAddr& dst) {
debugfn();
// Limit EAPOL packet size. The EAPOL packet's size depends on the link transport protocol and
// might exceed 255 octets. However, we don't support EAP yet and EAPOL Key frames are always
// shorter.
// TODO(hahnr): If necessary, find a better upper bound once we support EAP.
size_t len = sizeof(EapolFrame) + be16toh(eapol->packet_body_length);
if (len > 255) { return ZX_OK; }
auto ind = EapolIndication::New();
ind->data = ::fidl::Array<uint8_t>::New(len);
std::memcpy(ind->data.data(), eapol, len);
ind->src_addr = fidl::Array<uint8_t>::New(common::kMacAddrLen);
ind->dst_addr = fidl::Array<uint8_t>::New(common::kMacAddrLen);
src.CopyTo(ind->src_addr.data());
dst.CopyTo(ind->dst_addr.data());
size_t buf_len = sizeof(ServiceHeader) + ind->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::EAPOL_indication, ind);
if (status != ZX_OK) {
errorf("could not serialize EapolIndication: %d\n", status);
} else {
status = device_->SendService(std::move(packet));
}
return status;
}
zx_status_t Station::HandleMlmeSetKeysReq(const SetKeysRequest& req) {
debugfn();
for (auto& keyPtr : req.keylist) {
if (keyPtr.is_null() || keyPtr->key.is_null()) { return ZX_ERR_NOT_SUPPORTED; }
uint8_t key_type;
switch (keyPtr->key_type) {
case KeyType::PAIRWISE:
key_type = WLAN_KEY_TYPE_PAIRWISE;
break;
case KeyType::PEER_KEY:
key_type = WLAN_KEY_TYPE_PEER;
break;
case 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, keyPtr->key.data(), keyPtr->length);
key_config.key_type = key_type;
key_config.key_len = static_cast<uint8_t>(keyPtr->length);
key_config.key_idx = keyPtr->key_id;
key_config.protection = WLAN_PROTECTION_RX_TX;
key_config.cipher_type = keyPtr->cipher_suite_type;
memcpy(key_config.cipher_oui, keyPtr->cipher_suite_oui.data(),
sizeof(key_config.cipher_oui));
if (!keyPtr->address.is_null()) {
memcpy(key_config.peer_addr, keyPtr->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_ = 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; }
auto assoc_chan_num = channel().channel_num;
auto current_chan_num = device_->GetState()->channel().channel_num;
if (current_chan_num == assoc_chan_num) {
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; }
auto assoc_chan_num = channel().channel_num;
auto current_chan_num = device_->GetState()->channel().channel_num;
if (current_chan_num == assoc_chan_num) {
SetPowerManagementMode(false);
// TODO(hahnr): wait for TIM, and PS-POLL all buffered frames from AP.
}
return ZX_OK;
}
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(kData);
hdr->fc.set_subtype(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;
uint16_t seq = device_->GetState()->next_seq();
hdr->sc.set_seq(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(kControl);
frame->fc.set_subtype(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;
}
uint16_t Station::next_seq() {
uint16_t seq = device_->GetState()->next_seq();
if (seq == last_seq_) {
// If the sequence number has rolled over and back to the last seq number we sent to this
// station, increment again.
// IEEE Std 802.11-2016, 10.3.2.11.2, Table 10-3, Note TR1
seq = device_->GetState()->next_seq();
}
last_seq_ = seq;
return seq;
}
zx_time_t Station::deadline_after_bcn_period(zx_duration_t tus) {
ZX_DEBUG_ASSERT(!bss_.is_null());
return timer_->Now() + WLAN_TU(bss_->beacon_period * tus);
}
bool Station::IsHTReady() const {
// TODO(porce): Placeholder.
// bool bss_is_ht_capable = true;
// bool client_is_ht_capable = true;
// bool client_is_ht_config = true;
// return bss_is_ht_capable && client_is_ht_capable && client_is_ht_config;
// TODO(porce): GoogleGuest-Legacy fails to offer DHCP upon
// return true;
return false;
}
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.
hci.set_chan_width_set(HtCapabilityInfo::TWENTY_ONLY);
// hci.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
hci.set_sm_power_save(HtCapabilityInfo::DISABLED);
hci.set_greenfield(0);
hci.set_short_gi_20(1);
hci.set_short_gi_40(0);
hci.set_tx_stbc(1);
hci.set_rx_stbc(1); // one stream.
hci.set_delayed_block_ack(0);
hci.set_max_amsdu_len(HtCapabilityInfo::OCTETS_7935); // Aruba
// hci.set_max_amsdu_len(HtCapabilityInfo::OCTETS_3839); // TP-Link
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
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.
}
} // namespace wlan