blob: aa1c891eb5eb42bfa999bb80173a0a37abff93c3 [file] [log] [blame]
/*
* Copyright (c) 2019 The Fuchsia Authors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/sim/sim_fw.h"
#include <arpa/inet.h>
#include <fuchsia/hardware/wlan/info/c/banjo.h>
#include <fuchsia/hardware/wlanif/c/banjo.h>
#include <fuchsia/wlan/ieee80211/cpp/fidl.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <cstddef>
#include <memory>
#include <optional>
#include <third_party/bcmdhd/crossdriver/dhd.h>
#include <third_party/bcmdhd/crossdriver/wlioctl.h>
#include <wlan/common/mac_frame.h>
#include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-frame.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/bcdc.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/bits.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/brcm_hw_ids.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/brcmu_d11.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/cfg80211.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/common.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/debug.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/fweh.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/fwil.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/fwil_types.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/macros.h"
#include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/sim/sim.h"
#include "third_party/bcmdhd/crossdriver/include/proto/802.11.h"
namespace wlan::brcmfmac {
namespace wlan_ieee80211 = ::fuchsia::wlan::ieee80211;
#define SIM_FW_CHK_CMD_LEN(dcmd_len, exp_len) \
(((dcmd_len) < (exp_len)) ? ZX_ERR_INVALID_ARGS : ZX_OK)
SimFirmware::SimFirmware(brcmf_simdev* simdev, simulation::Environment* env)
: simdev_(simdev), hw_(env) {
// Configure the chanspec encode/decoder
d11_inf_.io_type = kIoType;
brcmu_d11_attach(&d11_inf_);
// Configure the (simulated) hardware => (simulated) firmware callbacks
SimHardware::EventHandlers handlers = {
.rx_handler = std::bind(&SimFirmware::Rx, this, std::placeholders::_1, std::placeholders::_2),
};
hw_.SetCallbacks(handlers);
country_code_ = {};
// The real FW always creates the first interface
struct brcmf_mbss_ssid_le default_mbss = {};
if (HandleIfaceTblReq(true, &default_mbss, nullptr, 0) != ZX_OK) {
ZX_PANIC("Unable to create default interface");
}
}
SimFirmware::~SimFirmware() = default;
simulation::StationIfc* SimFirmware::GetHardwareIfc() { return &hw_; }
void SimFirmware::GetChipInfo(uint32_t* chip, uint32_t* chiprev) {
*chip = BRCM_CC_4356_CHIP_ID;
*chiprev = 2;
}
int32_t SimFirmware::GetPM() { return power_mode_; }
zx_status_t SimFirmware::BusPreinit() {
// Currently nothing to do
return ZX_OK;
}
void SimFirmware::BusStop() { BRCMF_ERR("%s unimplemented", __FUNCTION__); }
// Returns a buffer that can be used for BCDC-formatted communications, with the requested
// payload size and an initialized BCDC header. "data_offset" represents any signalling offset
// (in words) and "offset_out" represents the offset of the payload within the returned buffer.
std::unique_ptr<std::vector<uint8_t>> SimFirmware::CreateBcdcBuffer(int16_t ifidx,
size_t requested_size,
size_t* offset_out) {
size_t header_size = sizeof(brcmf_proto_bcdc_header);
size_t total_size = header_size + requested_size;
auto buf = std::make_unique<std::vector<uint8_t>>(total_size);
auto header = reinterpret_cast<brcmf_proto_bcdc_header*>(buf->data());
header->flags = (BCDC_PROTO_VER << BCDC_FLAG_VER_SHIFT) & BCDC_FLAG_VER_MASK;
header->priority = 0xff & BCDC_PRIORITY_MASK;
header->flags2 = 0;
BCDC_SET_IF_IDX(header, ifidx);
// Data immediately follows the header
header->data_offset = 0;
*offset_out = header_size;
return buf;
}
// Set or get the value of an iovar. The format of the message is a null-terminated string
// containing the iovar name, followed by the value to assign to that iovar.
zx_status_t SimFirmware::BcdcVarOp(uint16_t ifidx, brcmf_proto_bcdc_dcmd* dcmd, uint8_t* data,
size_t len, bool is_set) {
zx_status_t status = ZX_OK;
if (is_set) {
// The command consists of a NUL-terminated name, followed by a value.
const char* const name_begin = reinterpret_cast<char*>(data);
const char* const name_end = static_cast<const char*>(memchr(name_begin, '\0', dcmd->len));
if (name_end == nullptr) {
BRCMF_DBG(SIM, "SET_VAR: iovar name not null-terminated");
return ZX_ERR_INVALID_ARGS;
}
const char* const value_begin = name_end + 1;
const size_t value_size = dcmd->len - (value_begin - name_begin);
// Since we're passing the value as a buffer down to users that may expect to be able to cast
// directly into it, make a suitably aligned copy here.
static constexpr auto align_val = static_cast<std::align_val_t>(alignof(std::max_align_t));
const auto aligned_delete = [](char* buffer) { operator delete[](buffer, align_val); };
std::unique_ptr<char, decltype(aligned_delete)> value_buffer(
static_cast<char*>(operator new[](value_size, align_val)), aligned_delete);
std::memcpy(value_buffer.get(), value_begin, value_size);
// IovarsSet returns the input unchanged
status = IovarsSet(ifidx, name_begin, value_buffer.get(), value_size, &dcmd->status);
} else {
// IovarsGet modifies the buffer in-place
status = IovarsGet(ifidx, reinterpret_cast<const char*>(data), data, dcmd->len, &dcmd->status);
if (dcmd->status != BCME_OK) {
dcmd->flags |= BCDC_DCMD_ERROR;
}
BRCMF_DBG(SIM, "dcmd->status=%s dcmd->flags=0x%08x", brcmf_fil_get_errstr(dcmd->status),
dcmd->flags);
}
if (status == ZX_OK) {
if (dcmd->status != BCME_OK)
dcmd->flags |= BCDC_DCMD_ERROR;
bcdc_response_.Set(reinterpret_cast<uint8_t*>(dcmd), len);
} else {
// Return empty message on failure
bcdc_response_.Clear();
}
return status;
}
// Process a TX CTL message. These have a BCDC header, followed by a payload that is determined
// by the type of command.
zx_status_t SimFirmware::BusTxCtl(unsigned char* msg, unsigned int len) {
brcmf_proto_bcdc_dcmd* dcmd;
constexpr size_t hdr_size = sizeof(struct brcmf_proto_bcdc_dcmd);
uint32_t value;
uint32_t ifidx;
if (len < hdr_size) {
BRCMF_DBG(SIM, "Message length (%u) smaller than BCDC header size (%zd)", len, hdr_size);
return ZX_ERR_INVALID_ARGS;
}
dcmd = reinterpret_cast<brcmf_proto_bcdc_dcmd*>(msg);
// The variable-length payload immediately follows the header
uint8_t* data = reinterpret_cast<uint8_t*>(dcmd) + hdr_size;
size_t data_len = len - hdr_size;
if (dcmd->len > data_len) {
BRCMF_DBG(SIM, "BCDC total message length (%zd) exceeds buffer size (%u)", dcmd->len + hdr_size,
len);
// The real firmware allows the true buffer size (dcmd->len) to exceed the length of the txctl
// itself (len - hdr_size). For an iovar get, we know this is allowed, so the sim firmware
// should let such a call through.
if (dcmd->cmd != BRCMF_C_GET_VAR) {
return ZX_ERR_INVALID_ARGS;
}
}
// Retrieve ifidx from the command and validate if the corresponding
// IF entry was previously allocated.
ifidx = BCDC_DCMD_IFIDX(dcmd->flags);
if (ifidx >= kMaxIfSupported || !iface_tbl_[ifidx].allocated) {
BRCMF_DBG(SIM, "IF idx: %d invalid or not allocated", ifidx);
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
bcme_status_t fw_err;
if (err_inj_.CheckIfErrInjCmdEnabled(dcmd->cmd, &status, &fw_err, ifidx)) {
if (status == ZX_OK) {
// If the transmission status is ZX_OK, customize the firmware error code which will be
// sent back through bcdc response.
if (fw_err != BCME_OK)
dcmd->flags |= BCDC_DCMD_ERROR;
dcmd->status = fw_err;
bcdc_response_.Set(msg, len);
}
// If the transmission status is not ZX_OK, return the error for this command transmission.
return status;
}
status = ZX_OK;
switch (dcmd->cmd) {
// Get/Set a firmware IOVAR. This message is comprised of a NULL-terminated string
// for the variable name, followed by the value to assign to it.
case BRCMF_C_SET_VAR:
case BRCMF_C_GET_VAR:
return BcdcVarOp(ifidx, dcmd, data, len, dcmd->cmd == BRCMF_C_SET_VAR);
break;
case BRCMF_C_GET_BANDLIST: {
const uint32_t bandlist[] = {
2, // Number of bands
WLC_BAND_2G,
WLC_BAND_5G,
};
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(bandlist))) == ZX_OK) {
memcpy(data, bandlist, sizeof(bandlist));
}
break;
}
case BRCMF_C_GET_REVINFO: {
struct brcmf_rev_info_le rev_info;
hw_.GetRevInfo(&rev_info);
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(rev_info))) == ZX_OK) {
memcpy(data, &rev_info, sizeof(rev_info));
}
break;
}
case BRCMF_C_GET_VERSION: {
// GET_VERSION is a bit of a misnomer. It's really the 802.11 supported spec
// (e.g., n or ac).
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(kIoType))) == ZX_OK) {
std::memcpy(data, &kIoType, sizeof(kIoType));
}
break;
}
case BRCMF_C_GET_GET_PKTCNTS: {
if (!iface_tbl_[kClientIfidx].allocated) {
BRCMF_ERR("Client iface has not been allocated.");
return ZX_ERR_BAD_STATE;
}
// Fake packet counter arbitrary values.
const brcmf_pktcnt_le sim_pktcnt = {
.rx_good_pkt = 5,
.rx_bad_pkt = 4,
.tx_good_pkt = 3,
.tx_bad_pkt = 2,
.rx_ocast_good_pkt = 1,
};
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(brcmf_pktcnt_le))) == ZX_OK) {
std::memcpy(data, &sim_pktcnt, sizeof(brcmf_pktcnt_le));
}
break;
}
case BRCMF_C_SET_PASSIVE_SCAN: {
// Specify whether to use a passive scan by default (instead of an active scan)
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(uint32_t))) == ZX_OK) {
value = *(reinterpret_cast<uint32_t*>(data));
default_passive_scan_ = value > 0;
}
break;
}
case BRCMF_C_SET_PROMISC:
// Set promiscuous mode
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(uint32_t))) == ZX_OK) {
value = *(reinterpret_cast<uint32_t*>(data));
ZX_ASSERT_MSG(!value, "Promiscuous mode not supported in simulator");
}
break;
case BRCMF_C_SET_SCAN_PASSIVE_TIME:
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(default_passive_time_))) == ZX_OK) {
default_passive_time_ = *(reinterpret_cast<uint32_t*>(data));
}
break;
case BRCMF_C_SET_PM:
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(power_mode_))) == ZX_OK) {
power_mode_ = *(reinterpret_cast<int32_t*>(data));
}
break;
case BRCMF_C_SET_SCAN_CHANNEL_TIME:
case BRCMF_C_SET_SCAN_UNASSOC_TIME:
BRCMF_DBG(SIM, "Ignoring firmware message %d", dcmd->cmd);
break;
case BRCMF_C_DISASSOC: {
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(brcmf_scb_val_le))) == ZX_OK) {
if (iface_tbl_[kClientIfidx].allocated && kClientIfidx == ifidx) {
// Initiate Disassoc from AP
auto scb_val = reinterpret_cast<brcmf_scb_val_le*>(data);
auto req_bssid = reinterpret_cast<common::MacAddr*>(scb_val->ea);
if (!assoc_state_.opts) {
BRCMF_DBG(SIM, "BRCMF_C_DISASSOC issued without assoc - ignore");
break;
}
common::MacAddr bssid(assoc_state_.opts->bssid);
ZX_ASSERT(bssid == *req_bssid);
DisassocLocalClient(scb_val->val);
} else if (softap_ifidx_ != std::nullopt && softap_ifidx_ == ifidx) {
BRCMF_ERR("This iovar is not expected to be used on softAP iface.");
return ZX_ERR_IO_REFUSED;
}
} else {
// Triggered by link down event in driver (no data)
if (assoc_state_.state == AssocState::ASSOCIATED) {
SetAssocState(AssocState::NOT_ASSOCIATED);
}
status = ZX_OK;
}
break;
}
case BRCMF_C_SCB_DEAUTHENTICATE_FOR_REASON: {
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(brcmf_scb_val_le))) == ZX_OK) {
status = ZX_ERR_IO;
if (softap_ifidx_ != std::nullopt && softap_ifidx_ == ifidx) {
auto scb_val = reinterpret_cast<brcmf_scb_val_le*>(data);
auto req_mac = reinterpret_cast<common::MacAddr*>(scb_val->ea);
auto client = FindClient(*req_mac);
if (client) {
// Deauthenticate also disassocs the client
SendEventToDriver(0, nullptr, BRCMF_E_DEAUTH, BRCMF_E_STATUS_SUCCESS,
softap_ifidx_.value(), nullptr, 0, 0, *req_mac);
SendEventToDriver(0, nullptr, BRCMF_E_DISASSOC, BRCMF_E_STATUS_SUCCESS,
softap_ifidx_.value(), nullptr, 0, 0, *req_mac);
iface_tbl_[softap_ifidx_.value()].ap_config.clients.remove(client);
status = ZX_OK;
}
}
}
break;
}
case BRCMF_C_SET_ROAM_TRIGGER:
case BRCMF_C_SET_ROAM_DELTA:
break;
case BRCMF_C_UP:
// The value in the IOVAR does not matter (according to Broadcom)
// TODO(karthikrish) Use dev_is_up_ to disable Tx, Rx, etc.
dev_is_up_ = true;
break;
case BRCMF_C_DOWN: {
// The value in the IOVAR does not matter (according to Broadcom)
// If any of the IFs are operational (i.e., client is associated or
// softap is started) disconnect as appropriate.
if (softap_ifidx_ != std::nullopt) {
StopSoftAP(softap_ifidx_.value());
}
DisassocLocalClient(BRCMF_E_REASON_LINK_DISASSOC);
dev_is_up_ = false;
break;
}
case BRCMF_C_SET_INFRA:
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(uint32_t))) == ZX_OK) {
iface_tbl_[ifidx].ap_config.infra_mode = *(reinterpret_cast<uint32_t*>(data));
}
break;
case BRCMF_C_SET_AP:
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(uint32_t))) == ZX_OK) {
value = *(reinterpret_cast<uint32_t*>(data));
#ifdef USE_MFG_FW
// Manufacturer firmware does not allow turning off AP if it is already off
if (!value && !iface_tbl_[ifidx].ap_mode) {
status = ZX_ERR_IO_REFUSED;
break;
}
#endif
if (value) {
ZX_ASSERT_MSG(iface_tbl_[ifidx].ap_config.infra_mode, "Only Infra mode AP is supported");
iface_tbl_[ifidx].ap_mode = true;
} else
iface_tbl_[ifidx].ap_mode = false;
softap_ifidx_ = std::nullopt;
}
break;
case BRCMF_C_SET_BCNPRD:
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(uint32_t))) == ZX_OK) {
// Beacon period
iface_tbl_[ifidx].ap_config.beacon_period = *(reinterpret_cast<uint32_t*>(data));
}
break;
case BRCMF_C_SET_DTIMPRD:
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(uint32_t))) == ZX_OK) {
// DTIM
iface_tbl_[ifidx].ap_config.dtim_period = *(reinterpret_cast<uint32_t*>(data));
}
break;
case BRCMF_C_SET_SSID: {
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(brcmf_join_params))) == ZX_OK) {
auto join_params = (reinterpret_cast<brcmf_join_params*>(data));
if (iface_tbl_[ifidx].ap_mode == true) {
iface_tbl_[ifidx].ap_config.ssid = join_params->ssid_le;
if (join_params->ssid_le.SSID_len) {
// non-zero SSID - assume AP start
ZX_ASSERT(iface_tbl_[ifidx].ap_config.ap_started == false);
// Schedule a Link Event to be sent to driver (simulating behviour
// in real HW).
ScheduleLinkEvent(kStartAPConfDelay, ifidx);
iface_tbl_[ifidx].ap_config.ap_started = true;
softap_ifidx_ = ifidx;
// Set the channel to the value specified in "chanspec" iovar
wlan_channel_t channel;
chanspec_to_channel(&d11_inf_, iface_tbl_[ifidx].chanspec, &channel);
hw_.SetChannel(channel);
// And Enable Rx
hw_.EnableRx();
} else {
// AP stop
ZX_ASSERT(iface_tbl_[ifidx].ap_config.ap_started == true);
StopSoftAP(ifidx);
BRCMF_DBG(SIM, "AP Stop processed");
}
} else {
// When iface_tbl_[ifidx].ap_mode == false, start an association
ZX_ASSERT(join_params->params_le.chanspec_num == 1);
if (ifidx != kClientIfidx) {
BRCMF_ERR("Not starting an assoc on client iface.");
return ZX_ERR_INVALID_ARGS;
}
// This is a start point of authentication and association operation, make sure the client
// iface is allocated.
if (!iface_tbl_[kClientIfidx].allocated) {
BRCMF_ERR("client iface has not been allocated.");
return ZX_ERR_BAD_STATE;
}
auto assoc_opts = std::make_unique<AssocOpts>();
wlan_channel_t channel;
chanspec_to_channel(&d11_inf_, join_params->params_le.chanspec_list[0], &channel);
iface_tbl_[ifidx].chanspec = join_params->params_le.chanspec_list[0];
memcpy(assoc_opts->bssid.byte, join_params->params_le.bssid, ETH_ALEN);
assoc_opts->ssid.len = join_params->ssid_le.SSID_len;
memcpy(assoc_opts->ssid.ssid, join_params->ssid_le.SSID, IEEE80211_MAX_SSID_LEN);
AssocInit(std::move(assoc_opts), channel);
BRCMF_DBG(SIM, "Auth start from C_SET_SSID");
// Schedule AuthStart to break the call chain for SAE authentication.
hw_.RequestCallback(std::bind(&SimFirmware::AuthStart, this), zx::msec(0));
}
}
break;
}
case BRCMF_C_GET_RSSI: {
if ((status = SIM_FW_CHK_CMD_LEN(dcmd->len, sizeof(int32_t))) == ZX_OK) {
int32_t rssi = -20;
std::memcpy(data, &rssi, sizeof(rssi));
}
break;
}
case BRCMF_C_SCAN: {
// For now this command is not used to start any kind of scan, the iovar "escan" is the entry
// of scan operation. This command is now only used to abort an escan when channel[0] in the
// parameter is -1.
auto scan_params = (reinterpret_cast<brcmf_scan_params_le*>(data));
ZX_ASSERT_MSG(scan_params->channel_list[0] == static_cast<uint16_t>(-1),
"BRCMF_C_SCAN should only be used for aborting an escan now.");
// Abort the scan. If there is no scan in progress, this becomes a nop.
if (scan_state_.state == ScanState::SCANNING) {
hw_.RequestCallback(std::bind(&SimFirmware::ScanComplete, this, BRCMF_E_STATUS_ABORT),
kAbortScanDelay);
}
break;
}
default:
BRCMF_DBG(SIM, "Unimplemented firmware message %d", dcmd->cmd);
return ZX_ERR_NOT_SUPPORTED;
}
if (status == ZX_OK) {
bcdc_response_.Set(msg, len);
}
return status;
}
zx_status_t SimFirmware::BusTxData(struct brcmf_netbuf* netbuf) {
if (netbuf->len < BCDC_HEADER_LEN + sizeof(ethhdr)) {
BRCMF_DBG(SIM, "Data netbuf (%u) smaller than BCDC + ethernet header %lu\n", netbuf->len,
BCDC_HEADER_LEN + sizeof(ethhdr));
return ZX_ERR_INVALID_ARGS;
}
// Get ifidx from bcdc header
brcmf_proto_bcdc_header* bcdc_header = (struct brcmf_proto_bcdc_header*)(netbuf);
uint16_t ifidx = BCDC_GET_IF_IDX(bcdc_header);
// For now we only support data transmission for client iface, another logic path should be added
// if we need to support data transmission for softap iface.
ZX_ASSERT_MSG(ifidx == kClientIfidx,
"Only data transmission for client iface is supported for now.");
// Ignore the BCDC Header
ethhdr* ethFrame = reinterpret_cast<ethhdr*>(netbuf->data + BCDC_HEADER_LEN);
// Build MAC frame
simulation::SimQosDataFrame dataFrame{};
// we can't send data frames if we aren't associated with anything
if (assoc_state_.opts == nullptr) {
return ZX_ERR_BAD_STATE;
}
// IEEE Std 802.11-2016, 9.4.1.4
switch (assoc_state_.opts->bss_type) {
case WLAN_BSS_TYPE_IBSS:
// We don't support IBSS
ZX_ASSERT_MSG(false, "Non-infrastructure types not currently supported by sim-fw\n");
dataFrame.toDS_ = 0;
dataFrame.fromDS_ = 0;
dataFrame.addr1_ = common::MacAddr(ethFrame->h_dest);
dataFrame.addr2_ = common::MacAddr(ethFrame->h_source);
dataFrame.addr3_ = assoc_state_.opts->bssid;
break;
case WLAN_BSS_TYPE_ANY_BSS:
// It seems that our driver typically uses this with the intention the the firmware will treat
// it as an infrastructure bss, so we'll do the same
case WLAN_BSS_TYPE_INFRASTRUCTURE:
dataFrame.toDS_ = 1;
dataFrame.fromDS_ = 0;
dataFrame.addr1_ = assoc_state_.opts->bssid;
dataFrame.addr2_ = common::MacAddr(ethFrame->h_source);
dataFrame.addr3_ = common::MacAddr(ethFrame->h_dest);
// Sim FW currently doesn't distinguish QoS from non-QoS association. If it does, this should
// only be set for a QoS association.
dataFrame.qosControl_ = netbuf->priority;
break;
default:
// TODO: support other bss types such as Mesh
ZX_ASSERT_MSG(false, "Non-infrastructure types not currently supported by sim-fw\n");
break;
}
// For now, since the LLC information would always be the same aside from the redundant ethernet
// type (Table M2 IEEE 802.11 2016). we will not append/parse LLC headers
uint32_t payload_size = netbuf->len - BCDC_HEADER_LEN - sizeof(ethhdr);
dataFrame.payload_.resize(payload_size);
memcpy(dataFrame.payload_.data(), netbuf->data + BCDC_HEADER_LEN + sizeof(ethhdr), payload_size);
hw_.Tx(dataFrame);
free(netbuf->allocated_buffer);
free(netbuf);
return ZX_OK;
}
// Stop the SoftAP
void SimFirmware::StopSoftAP(uint16_t ifidx) {
// Disassoc and remove all the associated clients
for (auto client : iface_tbl_[ifidx].ap_config.clients) {
simulation::SimDisassocReqFrame disassoc_req_frame(iface_tbl_[ifidx].mac_addr, client->mac_addr,
0);
hw_.Tx(disassoc_req_frame);
}
iface_tbl_[ifidx].ap_config.clients.clear();
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_SUCCESS, ifidx);
iface_tbl_[ifidx].ap_config.ap_started = false;
iface_tbl_[ifidx].chanspec = 0;
softap_ifidx_ = std::nullopt;
}
void SimFirmware::SendAPStartLinkEvent(uint16_t ifidx) {
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_SUCCESS, ifidx, nullptr,
BRCMF_EVENT_MSG_LINK);
}
void SimFirmware::ScheduleLinkEvent(zx::duration when, uint16_t ifidx) {
hw_.RequestCallback(std::bind(&SimFirmware::SendAPStartLinkEvent, this, ifidx),
when);
}
uint16_t SimFirmware::GetNumClients(uint16_t ifidx) {
if (softap_ifidx_ == std::nullopt) {
BRCMF_ERR("SoftAP iface has not been allocated or started.");
return 0;
}
if (ifidx != softap_ifidx_) {
BRCMF_ERR("Input iface is not SoftAP iface.");
return 0;
}
return iface_tbl_[softap_ifidx_.value()].ap_config.clients.size();
}
// Process an RX CTL message. We simply pass back the results of the previous TX CTL
// operation, which has been stored in bcdc_response_. In real hardware, we may have to
// indicate that the TX CTL operation has not completed. In simulated hardware, we perform
// all operations synchronously.
//
// This function is a simplified version of brcmf_sdio_bus_rxctl. As much of the
// response `msg_` last stored by BcdcResponse::Set() is written to `data` as possible,
// and the actual size of `msg_` is returned in `len_out`.
zx_status_t SimFirmware::BusRxCtl(unsigned char* msg, uint len, int* rxlen_out) {
if (bcdc_response_.IsClear()) {
BRCMF_ERR("no response available");
return ZX_ERR_UNAVAILABLE;
}
zx_status_t status = bcdc_response_.Get(msg, len, rxlen_out);
// Responses are not re-sent on subsequent requests
bcdc_response_.Clear();
return status;
}
struct pktq* SimFirmware::BusGetTxQueue() {
BRCMF_ERR("%s unimplemented", __FUNCTION__);
return nullptr;
}
zx_status_t SimFirmware::BusGetBootloaderMacAddr(uint8_t* mac_addr) {
// Simulate a fixed MAC address.
static uint8_t fixed_random_macaddr[ETH_ALEN] = {};
static bool memoized = false;
zx_status_t status;
// Use a value provided by the error injector, if one has been set
auto bootloader_mac_addr = err_inj_.BootloaderMacAddr();
if (bootloader_mac_addr) {
memcpy(mac_addr, bootloader_mac_addr->byte, ETH_ALEN);
return ZX_OK;
}
if (!memoized) {
BRCMF_INFO(
"Bootloader MAC address not available to simulated firmware. Generating a random mac "
"address to simulate a fixed MAC address.");
status = brcmf_gen_random_mac_addr(fixed_random_macaddr);
if (status != ZX_OK) {
return status;
}
BRCMF_INFO("Generated random mac address: " MAC_FMT_STR, MAC_FMT_ARGS(fixed_random_macaddr));
memoized = true;
}
memcpy(mac_addr, fixed_random_macaddr, sizeof(fixed_random_macaddr));
return ZX_OK;
}
void SimFirmware::BusSetTimer(std::function<void()> fn, zx_duration_t delay, uint64_t* id_out) {
zx::duration event_delay(delay);
hw_.RequestCallback(std::move(fn), event_delay, id_out);
}
void SimFirmware::BusCancelTimer(uint64_t id) { hw_.CancelCallback(id); }
void SimFirmware::BcdcResponse::Clear() { len_ = 0; }
zx_status_t SimFirmware::BcdcResponse::Get(uint8_t* msg, size_t len, int* rxlen_out) {
memcpy(msg, msg_, std::min(len, len_));
ZX_ASSERT_MSG(len_ <= INT_MAX, "Response length exceeds INT_MAX");
*rxlen_out = static_cast<int>(len_);
return ZX_OK;
}
bool SimFirmware::BcdcResponse::IsClear() { return len_ == 0; }
void SimFirmware::BcdcResponse::Set(uint8_t* data, size_t new_len) {
ZX_DEBUG_ASSERT(new_len <= sizeof(msg_));
len_ = new_len;
memcpy(msg_, data, new_len);
}
zx_status_t SimFirmware::HandleIfaceTblReq(const bool add_entry, const void* data,
uint8_t* iface_id, int32_t bsscfgidx) {
if (add_entry) {
// Allocate the first available entry
for (int i = 0; i < kMaxIfSupported; i++) {
if (!iface_tbl_[i].allocated) {
iface_tbl_[i].allocated = true;
iface_tbl_[i].iface_id = i;
iface_tbl_[i].bsscfgidx = bsscfgidx;
if (iface_id)
*iface_id = i;
return ZX_OK;
}
}
} else {
for (int i = 0; i < kMaxIfSupported; i++) {
if (iface_tbl_[i].allocated && iface_tbl_[i].bsscfgidx == bsscfgidx) {
if (iface_id)
*iface_id = iface_tbl_[i].iface_id;
// If AP is in started state, send disassoc req to all clients
if (iface_tbl_[i].ap_mode) {
if (iface_tbl_[i].ap_config.ap_started) {
BRCMF_DBG(SIM, "AP is still started...disassoc all clients");
for (auto client : iface_tbl_[i].ap_config.clients) {
simulation::SimDisassocReqFrame disassoc_req_frame(iface_tbl_[i].mac_addr,
client->mac_addr, 0);
hw_.Tx(disassoc_req_frame);
}
}
// Clear out the clients
iface_tbl_[i].ap_config.clients.clear();
softap_ifidx_ = std::nullopt;
}
iface_tbl_[i] = {};
BRCMF_DBG(SIM, "Interface Delete ifidx: %d done", i);
return ZX_OK;
}
}
}
return ZX_ERR_IO;
}
zx_status_t SimFirmware::HandleIfaceRequest(const bool add_iface, const void* data,
const size_t len, int32_t bsscfgidx) {
uint8_t iface_id;
size_t payload_size = sizeof(brcmf_if_event);
auto buf = std::make_unique<std::vector<uint8_t>>(payload_size);
uint8_t* buffer_data = buf->data();
struct brcmf_if_event* ifevent = reinterpret_cast<brcmf_if_event*>(buffer_data);
ifevent->role = 1;
if (HandleIfaceTblReq(add_iface, data, &iface_id, bsscfgidx) == ZX_OK) {
if (add_iface) {
ifevent->action = BRCMF_E_IF_ADD;
ifevent->bsscfgidx = bsscfgidx;
} else {
ifevent->action = BRCMF_E_IF_DEL;
ifevent->bsscfgidx = bsscfgidx;
}
ifevent->ifidx = iface_id;
char ifname[IFNAMSIZ];
sprintf(ifname, "wl0.%d", iface_id);
SendEventToDriver(payload_size, std::move(buf), BRCMF_E_IF, BRCMF_E_STATUS_SUCCESS, iface_id,
ifname);
} else {
SendEventToDriver(payload_size, std::move(buf), BRCMF_E_IF, BRCMF_E_STATUS_ERROR, 0);
}
return ZX_OK;
}
// Handle association request from a client to the SoftAP interface
// ifidx is expected to be a valid index (allocated and configured as AP)
#define TWO_ZERO_LEN_TLVS_LEN (4)
void SimFirmware::HandleAssocReq(std::shared_ptr<const simulation::SimAssocReqFrame> frame) {
auto buf = std::make_unique<std::vector<uint8_t>>(TWO_ZERO_LEN_TLVS_LEN);
uint8_t* tlv_buf = buf->data();
// The driver expects ssid and rsne in TLV format, just fake it for now
*tlv_buf++ = WLAN_IE_TYPE_SSID;
*tlv_buf++ = 0;
*tlv_buf++ = WLAN_IE_TYPE_RSNE;
*tlv_buf++ = 0;
// Check client state
auto client = FindClient(frame->src_addr_);
if (client) {
if (client->state == Client::ASSOCIATED) {
// Client already associated, send a REASSOC_IND event to driver
SendEventToDriver(TWO_ZERO_LEN_TLVS_LEN, std::move(buf), BRCMF_E_REASSOC_IND,
BRCMF_E_STATUS_SUCCESS, softap_ifidx_.value(), nullptr, 0, 0,
frame->src_addr_);
} else if (client->state == Client::AUTHENTICATED) {
// Indicate Assoc success to driver - by sendind ASSOC_IND
SendEventToDriver(TWO_ZERO_LEN_TLVS_LEN, std::move(buf), BRCMF_E_ASSOC_IND,
BRCMF_E_STATUS_SUCCESS, softap_ifidx_.value(), nullptr, 0, 0,
frame->src_addr_);
client->state = Client::ASSOCIATED;
}
simulation::SimAssocRespFrame assoc_resp_frame(frame->bssid_, frame->src_addr_,
wlan_ieee80211::StatusCode::SUCCESS);
hw_.Tx(assoc_resp_frame);
BRCMF_DBG(SIM, "Assoc done Num Clients : %lu",
iface_tbl_[softap_ifidx_.value()].ap_config.clients.size());
} else {
// Client cannot start association before getting authenticated.
// IEEE 802.11-2016 11.3.6.3 specifies that the SME issues a NOT_AUTHENTICATED in the
// MLME-AUTHENTICATE.response message here. Unfortunately, NOT_AUTHENTICATED is in the
// ReasonCode namespace, and the generated MAC frame requires a StatusCode for the association
// response frame. This is a known fault in 802.11-2016, that may be fixed in the next
// revision. Fortunately for us, the NOT_AUTHENTICATED value of 9 in the ReasonCode namespace
// falls in a reserved-unused value in the StatusCode namespace, so we will just return the
// casted value as (presumably) existing APs may err in doing.
simulation::SimAssocRespFrame assoc_resp_frame(
frame->bssid_, frame->src_addr_,
static_cast<wlan_ieee80211::StatusCode>(wlan_ieee80211::ReasonCode::NOT_AUTHENTICATED));
hw_.Tx(assoc_resp_frame);
BRCMF_DBG(SIM, "Assoc fail, should be authenticated first.");
}
}
void SimFirmware::AssocInit(std::unique_ptr<AssocOpts> assoc_opts, wlan_channel_t& channel) {
SetAssocState(AssocState::ASSOCIATING);
assoc_state_.opts = std::move(assoc_opts);
assoc_state_.num_attempts = 0;
uint16_t chanspec = channel_to_chanspec(&d11_inf_, &channel);
SetIFChanspec(kClientIfidx, chanspec);
hw_.SetChannel(channel);
hw_.EnableRx();
}
void SimFirmware::AssocScanResultSeen(const ScanResult& scan_result) {
std::optional<wlan_ssid_t> scan_result_ssid;
for (const auto& ie : scan_result.ies) {
if (ie != nullptr && ie->IeType() == simulation::InformationElement::IE_TYPE_SSID) {
auto ssid_ie = std::static_pointer_cast<simulation::SsidInformationElement>(ie);
scan_result_ssid.emplace(ssid_ie->ssid_);
}
}
// Check ssid filter
if (scan_state_.opts->ssid && scan_result_ssid.has_value()) {
ZX_ASSERT(scan_state_.opts->ssid->len <= sizeof(scan_state_.opts->ssid->ssid));
if (scan_result_ssid->len != scan_state_.opts->ssid->len) {
return;
}
if (std::memcmp(scan_result_ssid->ssid, scan_state_.opts->ssid->ssid, scan_result_ssid->len) !=
0) {
return;
}
}
// Check bssid filter
if ((scan_state_.opts->bssid) && (scan_result.bssid != *scan_state_.opts->bssid)) {
return;
}
assoc_state_.scan_results.push_back(scan_result);
}
void SimFirmware::AssocScanDone(brcmf_fweh_event_status_t event_status) {
ZX_ASSERT(assoc_state_.state == AssocState::SCANNING);
// Operation fails if we don't have at least one scan result
if ((event_status == BRCMF_E_STATUS_SUCCESS) && (assoc_state_.scan_results.size() == 0)) {
event_status = BRCMF_E_STATUS_NO_NETWORKS;
}
if (event_status != BRCMF_E_STATUS_SUCCESS) {
SendEventToDriver(0, nullptr, BRCMF_E_SET_SSID, event_status, kClientIfidx);
SetAssocState(AssocState::NOT_ASSOCIATED);
return;
}
// For now, just pick the first AP. The real firmware can select based on signal strength and
// band, but since wlanstack makes its own decision, we won't bother to model that here for now.
ScanResult& ap = assoc_state_.scan_results.front();
auto assoc_opts = std::make_unique<AssocOpts>();
assoc_opts->bssid = ap.bssid;
if (scan_state_.opts->ssid)
assoc_opts->ssid = scan_state_.opts->ssid.value();
AssocInit(std::move(assoc_opts), ap.channel);
// Send an event of the first scan result to driver when assoc scan is done.
EscanResultSeen(ap);
BRCMF_DBG(SIM, "Auth start after assoc scan");
AuthStart();
}
void SimFirmware::AssocClearContext() {
hw_.CancelCallback(assoc_state_.assoc_timer_id);
AuthClearContext();
SetAssocState(AssocState::NOT_ASSOCIATED);
assoc_state_.opts = nullptr;
assoc_state_.scan_results.clear();
// Clear out the channel setting
iface_tbl_[kClientIfidx].chanspec = 0;
assoc_resp_ies_len_ = 0;
memset(assoc_resp_ies_, 0, ASSOC_IES_MAX_LEN);
}
void SimFirmware::AuthClearContext() {
hw_.CancelCallback(auth_state_.auth_timer_id);
auth_state_.state = AuthState::NOT_AUTHENTICATED;
auth_state_.sec_type = simulation::SEC_PROTO_TYPE_OPEN;
}
void SimFirmware::AssocHandleFailure() {
if (assoc_state_.state == AssocState::NOT_ASSOCIATED) {
return;
}
if (assoc_state_.num_attempts >= assoc_max_retries_
// The firmware has been observed to not retry when there is an
// authentication challenge failure. Also when firmware is doing external supplicant SAE auth,
// it won't retry, because the retry is controlled by external supplicant.
|| assoc_state_.state == AssocState::AUTHENTICATION_CHALLENGE_FAILURE ||
auth_state_.sec_type == simulation::SEC_PROTO_TYPE_WPA3) {
BRCMF_DBG(SIM, "Assoc failed. Send E_SET_SSID with failure");
SendEventToDriver(0, nullptr, BRCMF_E_ASSOC, BRCMF_E_STATUS_FAIL, kClientIfidx, nullptr, 0, 30,
assoc_state_.opts->bssid);
SendEventToDriver(0, nullptr, BRCMF_E_SET_SSID, BRCMF_E_STATUS_FAIL, kClientIfidx);
AssocClearContext();
} else {
assoc_state_.num_attempts++;
auth_state_.state = AuthState::NOT_AUTHENTICATED;
BRCMF_DBG(SIM, "Assoc failed. Try again..attempt: %d", assoc_state_.num_attempts);
AuthStart();
}
}
void SimFirmware::AuthStart() {
BRCMF_DBG(SIM, "Auth Start");
common::MacAddr srcAddr(GetMacAddr(kClientIfidx));
common::MacAddr bssid(assoc_state_.opts->bssid);
hw_.RequestCallback(std::bind(&SimFirmware::AssocHandleFailure, this), kAuthTimeout,
&auth_state_.auth_timer_id);
ZX_ASSERT(auth_state_.state == AuthState::NOT_AUTHENTICATED);
simulation::SimAuthType auth_type = simulation::AUTH_TYPE_OPEN;
switch (iface_tbl_[kClientIfidx].auth_type) {
case BRCMF_AUTH_MODE_OPEN:
// Sequence number start from 0, end with at most 3.
auth_type = simulation::AUTH_TYPE_OPEN;
break;
case BRCMF_AUTH_MODE_AUTO:
auth_type = simulation::AUTH_TYPE_SHARED_KEY;
break;
case BRCMF_AUTH_MODE_SAE:
auth_type = simulation::AUTH_TYPE_SAE;
break;
default:
ZX_ASSERT_MSG(false, "Auth type not supported.");
}
simulation::SimAuthFrame auth_req_frame(srcAddr, bssid, 1, auth_type,
wlan_ieee80211::StatusCode::SUCCESS);
if (iface_tbl_[kClientIfidx].wsec == WEP_ENABLED) {
ZX_ASSERT(iface_tbl_[kClientIfidx].wpa_auth == WPA_AUTH_DISABLED);
auth_state_.sec_type = simulation::SEC_PROTO_TYPE_WEP;
}
switch (iface_tbl_[kClientIfidx].wpa_auth) {
case WPA_AUTH_PSK:
ZX_ASSERT((iface_tbl_[kClientIfidx].wsec & (uint32_t)WSEC_NONE) == 0U);
auth_state_.sec_type = simulation::SEC_PROTO_TYPE_WPA1;
break;
case WPA2_AUTH_PSK:
ZX_ASSERT((iface_tbl_[kClientIfidx].wsec & (uint32_t)WSEC_NONE) == 0U);
auth_state_.sec_type = simulation::SEC_PROTO_TYPE_WPA2;
break;
case WPA3_AUTH_SAE_PSK:
ZX_ASSERT((iface_tbl_[kClientIfidx].wsec & (uint32_t)WSEC_NONE) == 0U);
auth_state_.sec_type = simulation::SEC_PROTO_TYPE_WPA3;
break;
default:
ZX_ASSERT_MSG(auth_state_.sec_type == simulation::SEC_PROTO_TYPE_WEP ||
auth_state_.sec_type == simulation::SEC_PROTO_TYPE_OPEN,
"Upper layer auth type not supported.");
/* No action needed for WEP or OPEN*/
break;
}
if (auth_state_.sec_type == simulation::SEC_PROTO_TYPE_WPA3) {
auto buf = std::make_unique<std::vector<uint8_t>>(sizeof(brcmf_ext_auth));
auto ext_auth_data = reinterpret_cast<brcmf_ext_auth*>(buf->data());
// Set the data values, though driver only cares about the bssid.
ext_auth_data->ssid.SSID_len = assoc_state_.opts->ssid.len;
memcpy(ext_auth_data->ssid.SSID, assoc_state_.opts->ssid.ssid, IEEE80211_MAX_SSID_LEN);
bssid.CopyTo(ext_auth_data->bssid);
ext_auth_data->key_mgmt_suite = WPA3_AUTH_SAE_PSK;
ext_auth_data->status = BRCMF_E_STATUS_SUCCESS;
auth_state_.state = AuthState::EXPECTING_EXTERNAL_COMMIT;
SendEventToDriver(sizeof(brcmf_ext_auth), std::move(buf), BRCMF_E_JOIN_START,
BRCMF_E_STATUS_SUCCESS, kClientIfidx);
} else {
auth_req_frame.sec_proto_type_ = auth_state_.sec_type;
auth_state_.state = AuthState::EXPECTING_SECOND;
hw_.Tx(auth_req_frame);
}
}
// The authentication frame might be an authentication response for client iface, or it could also
// be an authentication req from a potential client for softap iface.
void SimFirmware::RxAuthFrame(std::shared_ptr<const simulation::SimAuthFrame> frame) {
if (frame->sec_proto_type_ == simulation::SEC_PROTO_TYPE_WPA3) {
ZX_ASSERT(frame->auth_type_ == simulation::AUTH_TYPE_SAE);
RemoteUpdateExternalSaeStatus(frame->seq_num_, frame->status_, frame->payload_.data(),
frame->payload_.size());
} else {
if (frame->seq_num_ == 1) {
HandleAuthReq(frame);
} else {
HandleAuthResp(frame);
}
}
}
// Handling authentication request for potential client of softap iface.
void SimFirmware::HandleAuthReq(std::shared_ptr<const simulation::SimAuthFrame> frame) {
if (softap_ifidx_ == std::nullopt) {
BRCMF_DBG(SIM, "SoftAP iface has not been allocated or started.");
return;
}
ZX_ASSERT_MSG(iface_tbl_[softap_ifidx_.value()].auth_type == BRCMF_AUTH_MODE_OPEN,
"The only auth type we support now is BRCMF_AUTH_MODE_OPEN because softAP iface "
"only support OPEN and WPA2.");
// Refuse auth req with auth type other than AUTH_TYPE_OPEN.
if (frame->auth_type_ != simulation::AUTH_TYPE_OPEN) {
BRCMF_DBG(SIM, "SoftAP iface only support OPEN mode for authentication now.");
// Send authentication response back
simulation::SimAuthFrame auth_resp_frame(
iface_tbl_[softap_ifidx_.value()].mac_addr, frame->src_addr_, 2, frame->auth_type_,
wlan_ieee80211::StatusCode::REFUSED_REASON_UNSPECIFIED);
hw_.Tx(auth_resp_frame);
return;
}
auto client = std::make_shared<Client>(frame->src_addr_, Client::AUTHENTICATED);
// Add the client to the list
iface_tbl_[softap_ifidx_.value()].ap_config.clients.push_back(client);
// Send authentication response back
simulation::SimAuthFrame auth_resp_frame(iface_tbl_[softap_ifidx_.value()].mac_addr,
frame->src_addr_, 2, simulation::AUTH_TYPE_OPEN,
wlan_ieee80211::StatusCode::SUCCESS);
hw_.Tx(auth_resp_frame);
// AUTH_IND is a simple event with the source mac address included
SendEventToDriver(0, nullptr, BRCMF_E_AUTH_IND, BRCMF_E_STATUS_SUCCESS, softap_ifidx_.value(),
nullptr, 0, 0, frame->src_addr_);
}
void SimFirmware::HandleAuthResp(std::shared_ptr<const simulation::SimAuthFrame> frame) {
// If we are not expecting auth resp packets, ignore it,
if (auth_state_.state == AuthState::NOT_AUTHENTICATED ||
auth_state_.state == AuthState::AUTHENTICATED) {
return;
}
// Ignore if this is not intended for us
common::MacAddr mac_addr(GetMacAddr(kClientIfidx));
if (frame->dst_addr_ != mac_addr) {
return;
}
// Ignore if this is not from the bssid with which we were trying to authenticate
if (frame->src_addr_ != assoc_state_.opts->bssid) {
return;
}
// It should not be an auth req frame if its dst addr is a client
if (frame->seq_num_ != 2 && frame->seq_num_ != 4) {
return;
}
// Response received, cancel timer
hw_.CancelCallback(auth_state_.auth_timer_id);
if (iface_tbl_[kClientIfidx].auth_type == BRCMF_AUTH_MODE_OPEN) {
ZX_ASSERT(auth_state_.state == AuthState::EXPECTING_SECOND);
ZX_ASSERT(frame->seq_num_ == 2);
if (frame->status_ == wlan_ieee80211::StatusCode::REFUSED_REASON_UNSPECIFIED) {
BRCMF_DBG(SIM, "Auth refused, Handle failure");
AssocHandleFailure();
return;
}
auth_state_.state = AuthState::AUTHENTICATED;
// Remember the last auth'd bssid
auth_state_.bssid = assoc_state_.opts->bssid;
BRCMF_DBG(SIM, "Assoc Start after auth");
AssocStart();
} else {
// When iface_tbl_[kClientIfidx].auth_type == BRCMF_AUTH_MODE_AUTO
if (auth_state_.state == AuthState::EXPECTING_SECOND && frame->seq_num_ == 2) {
// Retry with AUTH_TYPE_OPEN_SYSTEM when refused in AUTH_TYPE_SHARED_KEY mode
if (frame->status_ == wlan_ieee80211::StatusCode::REFUSED_REASON_UNSPECIFIED) {
auth_state_.state = AuthState::NOT_AUTHENTICATED;
iface_tbl_[kClientIfidx].auth_type = BRCMF_AUTH_MODE_OPEN;
BRCMF_DBG(SIM, "Auth shared refused...try with OPEN");
AuthStart();
return;
}
// Challenge failure should not occur until the fourth frame.
ZX_ASSERT(frame->status_ != wlan_ieee80211::StatusCode::CHALLENGE_FAILURE);
// If we receive the second auth frame when we are expecting it, we send out the third one and
// set another timer for it.
hw_.RequestCallback(std::bind(&SimFirmware::AssocHandleFailure, this), kAuthTimeout,
&auth_state_.auth_timer_id);
auth_state_.state = AuthState::EXPECTING_FOURTH;
common::MacAddr srcAddr(GetMacAddr(kClientIfidx));
common::MacAddr bssid(assoc_state_.opts->bssid);
simulation::SimAuthFrame auth_req_frame(srcAddr, bssid, frame->seq_num_ + 1,
simulation::AUTH_TYPE_SHARED_KEY,
wlan_ieee80211::StatusCode::SUCCESS);
auth_req_frame.sec_proto_type_ = auth_state_.sec_type;
hw_.Tx(auth_req_frame);
} else if (auth_state_.state == AuthState::EXPECTING_FOURTH && frame->seq_num_ == 4) {
if (frame->status_ == wlan_ieee80211::StatusCode::CHALLENGE_FAILURE) {
assoc_state_.state = AssocState::AUTHENTICATION_CHALLENGE_FAILURE;
auth_state_.state = AuthState::NOT_AUTHENTICATED;
BRCMF_DBG(SIM, "Auth shared challenge failure, Handle failure");
SendEventToDriver(0, nullptr, BRCMF_E_AUTH, BRCMF_E_STATUS_FAIL, kClientIfidx, nullptr,
0, // TODO: determine what the flags should be
static_cast<uint16_t>(frame->status_), frame->src_addr_);
AssocHandleFailure();
return;
}
// If we receive the fourth auth frame when we are expecting it, start association
auth_state_.state = AuthState::AUTHENTICATED;
// Remember the last auth'd bssid
auth_state_.bssid = assoc_state_.opts->bssid;
BRCMF_DBG(SIM, "Assoc Start after 4way handshake");
AssocStart();
}
}
}
zx_status_t SimFirmware::RemoteUpdateExternalSaeStatus(uint16_t seq_num,
wlan_ieee80211::StatusCode status_code,
const uint8_t* sae_payload,
size_t text_len) {
ZX_ASSERT(sae_payload != nullptr);
if (seq_num != 1 && seq_num != 2) {
BRCMF_ERR("Invalid sequence number in SAE auth frame.");
return ZX_ERR_INVALID_ARGS;
}
if (seq_num == 1 && auth_state_.state != AuthState::EXPECTING_AP_COMMIT) {
BRCMF_ERR("Unexpected COMMIT auth frame from AP.");
return ZX_ERR_BAD_STATE;
}
if (seq_num == 2 && auth_state_.state != AuthState::EXPECTING_AP_CONFIRM) {
BRCMF_ERR("Unexpected CONFIRM auth frame from AP.");
return ZX_ERR_BAD_STATE;
}
// Frame received from AP, cancel timer.
hw_.CancelCallback(auth_state_.auth_timer_id);
// construct auth frame and send up to external supplicant.
size_t buf_size = sizeof(wlan::Authentication) + text_len;
auto buf = std::make_unique<std::vector<uint8_t>>(buf_size);
auto pframe_hdr = reinterpret_cast<wlan::Authentication*>(buf->data());
pframe_hdr->auth_txn_seq_number = seq_num;
pframe_hdr->status_code = WLAN_AUTH_RESULT_SUCCESS;
pframe_hdr->auth_algorithm_number = BRCMF_AUTH_MODE_SAE;
memcpy(buf->data() + sizeof(wlan::Authentication), sae_payload, text_len);
hw_.RequestCallback(std::bind(&SimFirmware::AssocHandleFailure, this), kAuthTimeout,
&auth_state_.auth_timer_id);
// Update state
if (seq_num == 1)
auth_state_.state = AuthState::EXPECTING_EXTERNAL_CONFIRM;
else
auth_state_.state = AuthState::EXPECTING_EXTERNAL_HANDSHAKE_RESP;
SendEventToDriver(buf_size, std::move(buf), BRCMF_E_AUTH, BRCMF_E_STATUS_SUCCESS, kClientIfidx);
return ZX_OK;
}
zx_status_t SimFirmware::LocalUpdateExternalSaeStatus(uint16_t seq_num,
wlan_ieee80211::StatusCode status_code,
const uint8_t* sae_payload, size_t text_len) {
ZX_ASSERT(sae_payload != nullptr);
if (seq_num != 1 && seq_num != 2) {
BRCMF_ERR("Invalid sequence number in SAE auth frame.");
return ZX_ERR_INVALID_ARGS;
}
common::MacAddr srcAddr(GetMacAddr(kClientIfidx));
common::MacAddr bssid(assoc_state_.opts->bssid);
// Create a template auth_req_frame, might be modified later.
simulation::SimAuthFrame auth_req_frame(srcAddr, bssid, 1, simulation::AUTH_TYPE_SAE,
wlan_ieee80211::StatusCode::SUCCESS);
if (seq_num == 1 && auth_state_.state != AuthState::EXPECTING_EXTERNAL_COMMIT) {
BRCMF_ERR("Unexpected COMMIT auth frame from external supplicant. seq_num: %u, state: %u",
seq_num, auth_state_.state);
return ZX_ERR_BAD_STATE;
}
if (seq_num == 2 && auth_state_.state != AuthState::EXPECTING_EXTERNAL_CONFIRM) {
BRCMF_ERR("Unexpected CONFIRM auth frame from external supplicant. seq_num: %u, state: %u",
seq_num, auth_state_.state);
return ZX_ERR_BAD_STATE;
}
// Frame received from external supplicant, cancel timer.
hw_.CancelCallback(auth_state_.auth_timer_id);
auth_req_frame.status_ = status_code;
auth_req_frame.seq_num_ = seq_num;
auth_req_frame.AddChallengeText(fbl::Span(sae_payload, text_len));
auth_req_frame.sec_proto_type_ = auth_state_.sec_type;
hw_.RequestCallback(std::bind(&SimFirmware::AssocHandleFailure, this), kAuthTimeout,
&auth_state_.auth_timer_id);
hw_.Tx(auth_req_frame);
// Update state
if (seq_num == 1)
auth_state_.state = AuthState::EXPECTING_AP_COMMIT;
else
auth_state_.state = AuthState::EXPECTING_AP_CONFIRM;
return ZX_OK;
}
// Remove the client from the list. If found return true else false.
bool SimFirmware::FindAndRemoveClient(const common::MacAddr client_mac, bool motivation_deauth,
uint16_t deauth_reason) {
if (softap_ifidx_ == std::nullopt) {
BRCMF_ERR("SoftAP iface has not been allocated or started.");
return false;
}
auto& clients = iface_tbl_[softap_ifidx_.value()].ap_config.clients;
for (auto client : clients) {
if (client->mac_addr == client_mac) {
if (motivation_deauth) {
// The removal is triggered by a deauth frame.
if (client->state == Client::AUTHENTICATED) {
// When this client is authenticated but not associated, only send up BRCMF_E_DEAUTH_IND
// to driver.
SendEventToDriver(0, nullptr, BRCMF_E_DEAUTH_IND, BRCMF_E_STATUS_SUCCESS,
softap_ifidx_.value(), nullptr, 0, deauth_reason, client_mac);
} else if (client->state == Client::ASSOCIATED) {
// When this client is associated, send both BRCMF_E_DEAUTH_IND and BRCMF_E_DISASSOC_IND
// events up to driver.
SendEventToDriver(0, nullptr, BRCMF_E_DEAUTH_IND, BRCMF_E_STATUS_SUCCESS,
softap_ifidx_.value(), nullptr, 0, deauth_reason, client_mac);
SendEventToDriver(0, nullptr, BRCMF_E_DISASSOC_IND, BRCMF_E_STATUS_SUCCESS,
softap_ifidx_.value(), nullptr, BRCMF_EVENT_MSG_LINK,
WLANIF_REASON_CODE_LEAVING_NETWORK_DISASSOC, client_mac);
}
} else {
BRCMF_DBG(SIM, "deauth_reason is not used.");
// The removal is triggered by a disassoc frame.
SendEventToDriver(0, nullptr, BRCMF_E_DISASSOC_IND, BRCMF_E_STATUS_SUCCESS,
softap_ifidx_.value(), nullptr, BRCMF_EVENT_MSG_LINK,
WLANIF_REASON_CODE_LEAVING_NETWORK_DISASSOC, client_mac);
}
clients.remove(client);
return true;
}
}
return false;
}
// Return true if client is in the assoc list else false
std::shared_ptr<SimFirmware::Client> SimFirmware::FindClient(const common::MacAddr client_mac) {
if (softap_ifidx_ == std::nullopt) {
BRCMF_ERR("SoftAP iface has not been allocated or started.");
return std::shared_ptr<SimFirmware::Client>(nullptr);
}
auto& clients = iface_tbl_[softap_ifidx_.value()].ap_config.clients;
for (auto& client : clients) {
if (client->mac_addr == client_mac) {
return client;
}
}
return std::shared_ptr<SimFirmware::Client>(nullptr);
}
std::vector<brcmf_wsec_key_le> SimFirmware::GetKeyList(uint16_t ifidx) {
return iface_tbl_[ifidx].wsec_key_list;
}
void SimFirmware::RxDeauthReq(std::shared_ptr<const simulation::SimDeauthFrame> frame) {
BRCMF_DBG(SIM, "Deauth from %s for %s reason: %d", MACSTR(frame->src_addr_),
MACSTR(frame->dst_addr_), frame->reason_);
// First check if this is a deauth meant for a client associated to our SoftAP
auto ifidx = GetIfidxByMac(frame->dst_addr_);
if (ifidx == -1) {
// Not meant for any of the valid IFs, ignore
return;
}
if (!iface_tbl_[ifidx].ap_mode) {
ZX_ASSERT_MSG(ifidx == kClientIfidx, "Not matching client iface.");
// Not meant for the SoftAP. Check if it is meant for the client interface
HandleDisconnectForClientIF(frame, auth_state_.bssid, frame->reason_);
return;
}
// Remove the client from the list (if found)
if (FindAndRemoveClient(frame->src_addr_, true, frame->reason_)) {
BRCMF_DBG(SIM, "Deauth done Num Clients: %lu", iface_tbl_[ifidx].ap_config.clients.size());
return;
}
BRCMF_DBG(SIM, "Deauth Client not found in List");
}
void SimFirmware::AssocStart() {
BRCMF_DBG(SIM, "Assoc Start");
common::MacAddr srcAddr(GetMacAddr(kClientIfidx));
hw_.RequestCallback(std::bind(&SimFirmware::AssocHandleFailure, this),
kAssocTimeout, &assoc_state_.assoc_timer_id);
// We can't use assoc_state_.opts->bssid directly because it may get free'd during TxAssocReq
// handling if a response is sent.
common::MacAddr bssid(assoc_state_.opts->bssid);
simulation::SimAssocReqFrame assoc_req_frame(srcAddr, bssid, assoc_state_.opts->ssid);
hw_.Tx(assoc_req_frame);
}
// Get the index of the SoftAP IF based on Mac.
int16_t SimFirmware::GetIfidxByMac(const common::MacAddr& addr) {
for (uint8_t i = 0; i < kMaxIfSupported; i++) {
if (iface_tbl_[i].allocated && iface_tbl_[i].mac_addr == addr) {
return i;
}
}
return -1;
}
// Get channel of IF
wlan_channel_t SimFirmware::GetIfChannel(bool is_ap) {
wlan_channel_t channel;
// Get chanspec
ZX_ASSERT_MSG(iface_tbl_[kClientIfidx].allocated, "The client iface is not allocated!");
uint16_t chanspec = iface_tbl_[kClientIfidx].chanspec;
ZX_ASSERT_MSG(chanspec != 0, "No chanspec assigned to client.");
// convert to channel
chanspec_to_channel(&d11_inf_, chanspec, &channel);
return channel;
}
// This routine for now only handles Disassoc Request meant for the SoftAP IF.
void SimFirmware::RxDisassocReq(std::shared_ptr<const simulation::SimDisassocReqFrame> frame) {
BRCMF_DBG(SIM, "Disassoc from %s for %s reason: %d", MACSTR(frame->src_addr_),
MACSTR(frame->dst_addr_), frame->reason_);
// First check if this is a disassoc meant for a client associated to our SoftAP
auto ifidx = GetIfidxByMac(frame->dst_addr_);
if (ifidx == -1) {
// Not meant for any of the valid IFs, ignore
return;
}
if (!iface_tbl_[ifidx].ap_mode) {
ZX_ASSERT_MSG(ifidx == kClientIfidx, "Not matching client iface.");
// Not meant for the SoftAP. Check if it is meant for the client interface
HandleDisconnectForClientIF(frame, assoc_state_.opts->bssid, frame->reason_);
return;
}
// Remove the client from the list (if found)
if (FindAndRemoveClient(frame->src_addr_, false)) {
BRCMF_DBG(SIM, "Disassoc done Num Clients: %lu", iface_tbl_[ifidx].ap_config.clients.size());
return;
}
BRCMF_DBG(SIM, "Client not found in List");
}
void SimFirmware::RxAssocResp(std::shared_ptr<const simulation::SimAssocRespFrame> frame) {
// Ignore if we are not trying to associate
if (assoc_state_.state != AssocState::ASSOCIATING) {
return;
}
// Ignore if this is not intended for us
if (GetIfidxByMac(frame->dst_addr_) == -1) {
return;
}
// Ignore if this is not from the bssid with which we were trying to associate
if (frame->src_addr_ != assoc_state_.opts->bssid) {
return;
}
// Response received, cancel timer
hw_.CancelCallback(assoc_state_.assoc_timer_id);
if (frame->status_ == wlan_ieee80211::StatusCode::SUCCESS) {
// IEEE Std 802.11-2016, 9.4.1.4 to determine bss type
bool capIbss = frame->capability_info_.ibss();
bool capEss = frame->capability_info_.ess();
if (capIbss && !capEss) {
ZX_ASSERT_MSG(false, "Non-infrastructure types not currently supported by sim-fw\n");
assoc_state_.opts->bss_type = WLAN_BSS_TYPE_IBSS;
} else if (!capIbss && capEss) {
assoc_state_.opts->bss_type = WLAN_BSS_TYPE_INFRASTRUCTURE;
} else if (capIbss && capEss) {
ZX_ASSERT_MSG(false, "Non-infrastructure types not currently supported by sim-fw\n");
assoc_state_.opts->bss_type = WLAN_BSS_TYPE_MESH;
} else {
BRCMF_WARN("Station with impossible capability not being an ess or ibss found\n");
}
// Set values that are returned for iovar "assoc_resp_ies"
ZX_ASSERT_MSG(frame->raw_ies_.size() <= ASSOC_IES_MAX_LEN,
"Assoc resp frame IEs longer than ASSOC_IES_MAX_LEN: %lu > %d",
frame->raw_ies_.size(), ASSOC_IES_MAX_LEN);
memcpy(assoc_resp_ies_, frame->raw_ies_.data(), frame->raw_ies_.size());
assoc_resp_ies_len_ = frame->raw_ies_.size();
BRCMF_DBG(SIM, "Assoc success, send events with a delay");
// Send the ASSOC event with a delay
SendEventToDriver(0, nullptr, BRCMF_E_ASSOC, BRCMF_E_STATUS_SUCCESS, kClientIfidx, nullptr, 0,
0, assoc_state_.opts->bssid, kAssocEventDelay);
// Send the LINK event with a delay
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_SUCCESS, kClientIfidx, nullptr,
BRCMF_EVENT_MSG_LINK, 0, assoc_state_.opts->bssid, kLinkEventDelay);
// Send the SSID event after a delay
SendEventToDriver(0, nullptr, BRCMF_E_SET_SSID, BRCMF_E_STATUS_SUCCESS, kClientIfidx, nullptr,
0, 0, assoc_state_.opts->bssid, kSsidEventDelay);
// Set the Assoc state only after E_ASSOC is sent to the driver.
hw_.RequestCallback(std::bind(&SimFirmware::SetAssocState, this, AssocState::ASSOCIATED),
kAssocEventDelay);
} else {
BRCMF_DBG(SIM, "Assoc refused, Handle failure");
AssocHandleFailure();
}
}
void SimFirmware::SetAssocState(AssocState::AssocStateName state) { assoc_state_.state = state; }
// Disassociate the Local Client (request coming in from the driver)
void SimFirmware::DisassocLocalClient(uint32_t reason) {
if (assoc_state_.state == AssocState::ASSOCIATED) {
common::MacAddr bssid(assoc_state_.opts->bssid);
common::MacAddr srcAddr(GetMacAddr(kClientIfidx));
// Transmit the disassoc req and since there is no response for it, indicate disassoc done to
// driver now
simulation::SimDisassocReqFrame disassoc_req_frame(srcAddr, bssid, reason);
hw_.Tx(disassoc_req_frame);
SetStateToDisassociated(reason, true);
} else {
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_FAIL, kClientIfidx);
}
AuthClearContext();
AssocClearContext();
}
// Disassoc/deauth Request from FakeAP for the Client IF.
void SimFirmware::HandleDisconnectForClientIF(
std::shared_ptr<const simulation::SimManagementFrame> frame, const common::MacAddr& bssid,
uint32_t reason) {
// Ignore if this is not intended for us
common::MacAddr mac_addr(iface_tbl_[kClientIfidx].mac_addr);
if (frame->dst_addr_ != mac_addr) {
return;
}
// Ignore if this is not from the bssid with which we are associated/authenticated
if (frame->src_addr_ != bssid) {
return;
}
if (frame->MgmtFrameType() == simulation::SimManagementFrame::FRAME_TYPE_DEAUTH) {
// The client could receive a deauth even after disassociation. Notify the driver always
SendEventToDriver(0, nullptr, BRCMF_E_DEAUTH_IND, BRCMF_E_STATUS_SUCCESS, kClientIfidx, 0, 0,
reason);
if (auth_state_.state == AuthState::AUTHENTICATED) {
AuthClearContext();
}
// DEAUTH implies disassoc, so continue
}
// disassoc
if (assoc_state_.state != AssocState::ASSOCIATED) {
// Already disassoc'd, nothing more to do.
return;
}
SetStateToDisassociated(reason, false);
AssocClearContext();
}
// precondition: was associated
void SimFirmware::SetStateToDisassociated(uint32_t reason, bool locally_initiated) {
// Disable beacon watchdog that triggers disconnect
DisableBeaconWatchdog();
// Send the appropriate event to driver.
SendEventToDriver(0, nullptr, locally_initiated ? BRCMF_E_DISASSOC : BRCMF_E_DISASSOC_IND,
BRCMF_E_STATUS_SUCCESS, kClientIfidx, nullptr, 0, reason,
assoc_state_.opts->bssid, kDisassocEventDelay);
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_SUCCESS, kClientIfidx, nullptr, 0,
reason, assoc_state_.opts->bssid, kLinkEventDelay);
}
// Assoc Request from Client for the SoftAP IF
void SimFirmware::RxAssocReq(std::shared_ptr<const simulation::SimAssocReqFrame> frame) {
BRCMF_DBG(SIM, "Assoc from %s for %s", MACSTR(frame->src_addr_), MACSTR(frame->bssid_));
if (softap_ifidx_ == std::nullopt) {
BRCMF_DBG(SIM,
"Received an assoc request but the softap iface has not been allocated or started.");
return;
}
if (std::memcmp(iface_tbl_[softap_ifidx_.value()].mac_addr.byte, frame->bssid_.byte, ETH_ALEN) ==
0) {
// ASSOC_IND contains some TLVs
HandleAssocReq(frame);
}
}
zx_status_t SimFirmware::HandleJoinRequest(const void* value, size_t value_len) {
auto join_params = reinterpret_cast<const brcmf_ext_join_params_le*>(value);
// Verify that the channel count is consistent with the size of the structure
size_t max_channels =
(value_len - offsetof(brcmf_ext_join_params_le, assoc_le.chanspec_list)) / sizeof(uint16_t);
size_t num_channels = join_params->assoc_le.chanspec_num;
if (max_channels < num_channels) {
BRCMF_DBG(SIM, "Bad join request: message size (%zd) too short for %zd channels", value_len,
num_channels);
return ZX_ERR_INVALID_ARGS;
}
if (assoc_state_.state != AssocState::NOT_ASSOCIATED) {
ZX_ASSERT_MSG(assoc_state_.state != AssocState::ASSOCIATED,
"Need to add support for automatically disassociating");
BRCMF_DBG(SIM, "Attempt to associate while association already in progress");
return ZX_ERR_BAD_STATE;
}
if (scan_state_.state != ScanState::STOPPED) {
BRCMF_DBG(SIM, "Attempt to associate while scan already in progress");
}
auto scan_opts = std::make_unique<ScanOpts>();
// scan_opts->sync_id is unused, since we're not reporting our results back to the driver
switch (join_params->scan_le.scan_type) {
case BRCMF_SCANTYPE_DEFAULT:
// Use the default
scan_opts->is_active = !default_passive_scan_;
break;
case BRCMF_SCANTYPE_PASSIVE:
scan_opts->is_active = false;
break;
case BRCMF_SCANTYPE_ACTIVE:
// FIXME: this should be true, but this is the mode used by the firmware and active scans are
// not supported yet.
scan_opts->is_active = false;
break;
default:
return ZX_ERR_INVALID_ARGS;
}
// Specify the SSID filter, if applicable
const struct brcmf_ssid_le* req_ssid = &join_params->ssid_le;
ZX_ASSERT(IEEE80211_MAX_SSID_LEN == sizeof(scan_opts->ssid->ssid));
if (req_ssid->SSID_len != 0) {
wlan_ssid_t ssid;
ssid.len = req_ssid->SSID_len;
std::copy(&req_ssid->SSID[0], &req_ssid->SSID[IEEE80211_MAX_SSID_LEN], ssid.ssid);
scan_opts->ssid = ssid;
}
// Specify BSSID filter, if applicable
common::MacAddr bssid(join_params->assoc_le.bssid);
if (!bssid.IsZero()) {
scan_opts->bssid = bssid;
}
// Determine dwell time
if (scan_opts->is_active) {
if (join_params->scan_le.active_time == static_cast<uint32_t>(-1)) {
// If we hit this, we need to determine how to set the default active time
ZX_ASSERT("Attempt to use default active scan time, but we don't know how to set this");
}
if (join_params->scan_le.active_time == 0) {
return ZX_ERR_INVALID_ARGS;
}
int32_t nprobes = (int32_t)join_params->scan_le.nprobes;
scan_opts->active_scan_max_attempts = nprobes <= 0 ? 1 : nprobes;
// Dwell time is equally split across the # of attempts
scan_opts->dwell_time =
zx::msec(join_params->scan_le.active_time) / scan_opts->active_scan_max_attempts;
BRCMF_DBG(SIM, "Join req - active scan: act time: %u nprobes: %d dwell: %zu",
join_params->scan_le.active_time, join_params->scan_le.nprobes,
scan_opts->dwell_time.get());
} else if (join_params->scan_le.passive_time == static_cast<uint32_t>(-1)) {
// Use default passive time
if (default_passive_time_ == static_cast<uint32_t>(-1)) {
// If we hit this, we need to determine the default default passive time
ZX_ASSERT("Attempt to use default passive scan time, but it hasn't been set yet");
}
scan_opts->dwell_time = zx::msec(default_passive_time_);
} else {
scan_opts->dwell_time = zx::msec(join_params->scan_le.passive_time);
}
// Copy channels from request
scan_opts->channels.resize(num_channels);
const uint16_t* chanspecs = &join_params->assoc_le.chanspec_list[0];
std::copy(&chanspecs[0], &chanspecs[num_channels], scan_opts->channels.data());
scan_opts->on_result_fn =
std::bind(&SimFirmware::AssocScanResultSeen, this, std::placeholders::_1);
scan_opts->on_done_fn = std::bind(&SimFirmware::AssocScanDone, this, std::placeholders::_1);
// Reset assoc state
SetAssocState(AssocState::SCANNING);
assoc_state_.scan_results.clear();
zx_status_t status = ScanStart(std::move(scan_opts));
if (status != ZX_OK) {
BRCMF_DBG(SIM, "Failed to start scan: %s", zx_status_get_string(status));
SetAssocState(AssocState::NOT_ASSOCIATED);
}
return status;
}
zx_status_t SimFirmware::SetIFChanspec(uint16_t ifidx, uint16_t chanspec) {
if (ifidx < 0 || ifidx >= kMaxIfSupported || !iface_tbl_[ifidx].allocated) {
return ZX_ERR_INVALID_ARGS;
}
if (iface_tbl_[ifidx].ap_mode) {
// When it's set for softAP, and if there is a client with a chanspec
if (!iface_tbl_[kClientIfidx].allocated || iface_tbl_[kClientIfidx].chanspec == 0) {
// If no client is activated, just set the chanspec
iface_tbl_[ifidx].chanspec = chanspec;
return ZX_OK;
}
// When a new softAP iface is created, set the chanspec to client iface chanspec, ignore
// the input.
iface_tbl_[ifidx].chanspec = iface_tbl_[kClientIfidx].chanspec;
return ZX_OK;
} else {
// If it's set for clients, change all chanspecs of existing ifaces into the same one(the one we
// want to set).
for (uint16_t i = 0; i < kMaxIfSupported; i++) {
if (iface_tbl_[i].allocated) {
// TODO(zhiyichen): If this operation change the chanspec for softAP iface, send out CSA
// announcement when there is any client connecting to it.
iface_tbl_[i].chanspec = chanspec;
}
}
}
return ZX_OK;
}
// Get the index of IF with the given bsscfgidx
int16_t SimFirmware::GetIfidxByBsscfgidx(int32_t bsscfgidx) {
for (uint8_t i = 0; i < kMaxIfSupported; i++) {
if (iface_tbl_[i].allocated && (bsscfgidx == iface_tbl_[i].bsscfgidx)) {
return i;
}
}
return -1;
}
// Stop the specified bss
zx_status_t SimFirmware::StopInterface(const int32_t bsscfgidx) {
int16_t ifidx = GetIfidxByBsscfgidx(bsscfgidx);
if (ifidx == -1)
return ZX_ERR_IO;
if (iface_tbl_[ifidx].ap_mode) {
StopSoftAP(ifidx);
} else {
DisassocLocalClient(BRCMF_E_REASON_LINK_DISASSOC);
}
return ZX_OK;
}
zx_status_t SimFirmware::IovarsSet(uint16_t ifidx, const char* name_buf, const void* value_buf,
size_t value_len, bcme_status_t* fw_err) {
uint8_t* value = (uint8_t*)value_buf;
int32_t bsscfgidx = 0;
char* name = (char*)name_buf;
// If it is a bsscfg iovar, handle it accordingly (strip the prefix and extract the
// bsscfgidx.
const size_t bsscfg_prefix_len = strlen(BRCMF_FWIL_BSSCFG_PREFIX);
if (!std::strncmp(name, BRCMF_FWIL_BSSCFG_PREFIX, bsscfg_prefix_len)) {
bsscfgidx = *(reinterpret_cast<const int32_t*>(value));
value += sizeof(bsscfgidx);
ZX_ASSERT(value_len >= sizeof(bsscfgidx));
value_len -= sizeof(bsscfgidx);
if (!value_len)
value = nullptr;
name += bsscfg_prefix_len;
}
// If Error Injection is enabled return with the appropriate status right away
zx_status_t status;
if (err_inj_.CheckIfErrInjIovarEnabled(name, &status, fw_err, nullptr, ifidx)) {
return status;
}
// Check against the entire command string.
size_t cmd_len = strlen(name);
if (!std::strncmp(name, "interface_remove", cmd_len)) {
return HandleIfaceRequest(false, value, value_len, bsscfgidx);
}
if (!std::strncmp(name, "ssid", cmd_len)) {
if (value_len < sizeof(brcmf_ssid_le)) {
return ZX_ERR_IO;
}
return HandleIfaceRequest(true, value, value_len, bsscfgidx);
}
if (!std::strncmp(name, "arp_ol", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_INVALID_ARGS;
}
iface_tbl_[ifidx].arp_ol = *(reinterpret_cast<const uint32_t*>(value));
return ZX_OK;
}
if (!std::strncmp(name, "arpoe", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_INVALID_ARGS;
}
iface_tbl_[ifidx].arpoe = *(reinterpret_cast<const uint32_t*>(value));
return ZX_OK;
}
if (!std::strncmp(name, "country", cmd_len)) {
auto cc_req = reinterpret_cast<const brcmf_fil_country_le*>(value);
country_code_ = *cc_req;
return ZX_OK;
}
if (!std::strncmp(name, "cur_etheraddr", cmd_len)) {
if (value_len == ETH_ALEN) {
return SetMacAddr(ifidx, reinterpret_cast<const uint8_t*>(value));
} else {
return ZX_ERR_INVALID_ARGS;
}
}
if (!std::strncmp(name, "escan", cmd_len)) {
// For now scanning on softAP iface is not supported yet.
if (ifidx != kClientIfidx) {
BRCMF_ERR("Not scanning with client iface.");
return ZX_ERR_INVALID_ARGS;
}
return HandleEscanRequest(reinterpret_cast<const brcmf_escan_params_le*>(value), value_len);
}
if (!std::strncmp(name, "join", cmd_len)) {
if (value_len < sizeof(brcmf_ext_join_params_le)) {
return ZX_ERR_IO;
}
if (ifidx != kClientIfidx) {
BRCMF_ERR("Not joining with client iface.");
return ZX_ERR_INVALID_ARGS;
}
// This is a start point of authentication and association operation, make sure the client
// iface is allocated.
if (!iface_tbl_[kClientIfidx].allocated) {
BRCMF_ERR("Client iface has not been allocated.");
return ZX_ERR_BAD_STATE;
}
// Don't cast yet because last element is variable length
return HandleJoinRequest(value, value_len);
}
if (!std::strncmp(name, "pfn_macaddr", cmd_len)) {
auto pfn_mac = reinterpret_cast<const brcmf_pno_macaddr_le*>(value);
memcpy(pfn_mac_addr_.byte, pfn_mac->mac, ETH_ALEN);
return ZX_OK;
}
if (!std::strncmp(name, "wsec", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
auto wsec = reinterpret_cast<const uint32_t*>(value);
iface_tbl_[ifidx].wsec = *wsec;
return ZX_OK;
}
if (!std::strncmp(name, "wsec_key", cmd_len)) {
if (value_len < sizeof(brcmf_wsec_key_le)) {
return ZX_ERR_IO;
}
auto wk_req = reinterpret_cast<const brcmf_wsec_key_le*>(value);
// Ensure that Primary Key does not have a mac address (all zeros)
if (wk_req->flags == BRCMF_PRIMARY_KEY) {
common::MacAddr zero_mac({0x0, 0x0, 0x0, 0x0, 0x0, 0x0});
ZX_ASSERT_MSG(std::memcmp(wk_req->ea, zero_mac.byte, ETH_ALEN) == 0,
"Group Key Mac should be all zeros");
}
std::vector<brcmf_wsec_key_le>& key_list = iface_tbl_[ifidx].wsec_key_list;
auto key_iter = std::find_if(key_list.begin(), key_list.end(),
[=](brcmf_wsec_key_le& k) { return k.index == wk_req->index; });
// If the key with same index exists, override it, if not, add a new key.
if (key_iter != key_list.end()) {
*key_iter = *wk_req;
} else {
// Use the first key index as current key index, in real case it will only change by AP.
if (key_list.empty())
iface_tbl_[ifidx].cur_key_idx = wk_req->index;
key_list.push_back(*wk_req);
}
return ZX_OK;
}
if (!std::strncmp(name, "assoc_retry_max", cmd_len)) {
auto assoc_max_retries = reinterpret_cast<const uint32_t*>(value);
assoc_max_retries_ = *assoc_max_retries;
return ZX_OK;
}
if (!std::strncmp(name, "chanspec", cmd_len)) {
if (value_len < sizeof(uint16_t)) {
return ZX_ERR_IO;
}
auto chanspec = reinterpret_cast<const uint16_t*>(value);
// TODO(karthikrish) Add multi channel support in SIM Env. For now ensure all IFs use the same
// channel
return (SetIFChanspec(ifidx, *chanspec));
}
if (!std::strncmp(name, "mpc", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
auto mpc = reinterpret_cast<const uint32_t*>(value);
// Ensure that mpc is never enabled when AP has been started
if (softap_ifidx_ != std::nullopt) {
// Ensure that mpc is 0 if the SoftAP has been started
ZX_ASSERT_MSG(*mpc == 0, "mpc should be 0 when SoftAP is active");
}
mpc_ = *mpc;
return ZX_OK;
}
if (!std::strncmp(name, "wpa_auth", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
auto wpa_auth = reinterpret_cast<const uint32_t*>(value);
iface_tbl_[ifidx].wpa_auth = *wpa_auth;
return ZX_OK;
}
if (!std::strncmp(name, "auth", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
auto auth = reinterpret_cast<const uint32_t*>(value);
iface_tbl_[ifidx].auth_type = *auth;
return ZX_OK;
}
if (!std::strncmp(name, "tlv", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
auto tlv = reinterpret_cast<const uint32_t*>(value);
iface_tbl_[ifidx].tlv = *tlv;
return ZX_OK;
}
if (!std::strcmp(name, "bss")) {
if (value_len < sizeof(brcmf_bss_ctrl)) {
return ZX_ERR_IO;
}
auto bss_info = reinterpret_cast<const brcmf_bss_ctrl*>(value);
// We do not know how non-zero value is handled in real FW.
ZX_ASSERT(bss_info->value == 0);
return StopInterface(bss_info->bsscfgidx);
}
if (!std::strncmp(name, "allmulti", cmd_len)) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_IO;
}
auto allmulti = reinterpret_cast<const uint32_t*>(value);
iface_tbl_[ifidx].allmulti = *allmulti;
return ZX_OK;
}
if (!std::strcmp(name, "assoc_mgr_cmd")) {
BRCMF_DBG(SIM, "Receive assoc_mgr_cmd in sim-fw.");
if (value_len < sizeof(assoc_mgr_cmd_t)) {
return ZX_ERR_IO;
}
ZX_ASSERT_MSG(ifidx == kClientIfidx, "SAE authentication only supported on client iface.");
auto cmd = reinterpret_cast<const assoc_mgr_cmd_t*>(value);
if (cmd->version != ASSOC_MGR_CURRENT_VERSION) {
BRCMF_INFO(
"Version number doesn't match the current one, but ignoring it in sim-fw for now.");
}
switch (cmd->cmd) {
case ASSOC_MGR_CMD_PAUSE_ON_EVT:
if (cmd->params == ASSOC_MGR_PARAMS_PAUSE_EVENT_AUTH_RESP) {
// When driver received JOIN_START event and successfully notified SME that the SAE
// authentication should start by sending up handshake indication, it will send a
// assoc_mgr_cmd with this parameter, telling firmware to expect the first authentication
// frame from external supplicant. Here we only check whether the auth_state is correct.
if (auth_state_.state != AuthState::EXPECTING_EXTERNAL_COMMIT) {
BRCMF_ERR(
"Incorrect state, START_AUTH event wasn't sent up to the driver, or the SAE "
"process has been started. Ignoring the command and return.");
return ZX_ERR_BAD_STATE;
}
} else if (cmd->params == ASSOC_MGR_PARAMS_EVENT_NONE) {
// Driver is trying to start association after finishing SAE authentication.
if (auth_state_.state != AuthState::EXPECTING_EXTERNAL_HANDSHAKE_RESP) {
BRCMF_ERR(
"Unexpected HANDSHAKE_RESP from external supplicant. Ignoring the command and "
"return.");
return ZX_ERR_BAD_STATE;
}
// Handshake resp received from external supplicant, cancel timer.
hw_.CancelCallback(auth_state_.auth_timer_id);
auth_state_.state = AuthState::AUTHENTICATED;
AssocStart();
return ZX_OK;
}
break;
case ASSOC_MGR_CMD_ABORT_ASSOC:
ZX_ASSERT_MSG(false, "Not supporting this command for assoc_mgr_cmd yet.");
break;
case ASSOC_MGR_CMD_SEND_AUTH: {
ZX_ASSERT_MSG(ifidx == kClientIfidx, "SAE authentication only supported on client iface.");
auto sae_frame = reinterpret_cast<const brcmf_sae_auth_frame*>(&cmd->params);
if (memcmp(sae_frame->mac_hdr.addr1.byte, assoc_state_.opts->bssid.byte, ETH_ALEN) ||
memcmp(sae_frame->mac_hdr.addr3.byte, assoc_state_.opts->bssid.byte, ETH_ALEN)) {
BRCMF_ERR(
"Dest addr does not match in SAE frame from external supplicant. Ignoring frame.");
return ZX_ERR_INVALID_ARGS;
}
// Authentication algorithm field must be SAE.
if (sae_frame->auth_hdr.auth_algorithm_number != BRCMF_AUTH_MODE_SAE) {
BRCMF_ERR("Authentication algorithm number does not match SAE algorithm number");
return ZX_ERR_INVALID_ARGS;
}
size_t sae_payload_length =
cmd->length - offsetof(struct brcmf_sae_auth_frame, sae_payload);
if ((status = LocalUpdateExternalSaeStatus(
sae_frame->auth_hdr.auth_txn_seq_number,
static_cast<wlan_ieee80211::StatusCode>(sae_frame->auth_hdr.status_code),
sae_frame->sae_payload, sae_payload_length)) != ZX_OK) {
BRCMF_ERR("Update SAE status failed with auth frame from external supllicant.");
return ZX_ERR_BAD_STATE;
}
break;
}
default:
ZX_ASSERT_MSG(false, "Command type %u is not currently supported for assoc_mgr_cmd.",
cmd->cmd);
break;
}
return ZX_OK;
}
// FIXME: For now, just pretend that we successfully set the value even when we did nothing
BRCMF_DBG(SIM, "Ignoring request to set iovar '%s'", name);
return ZX_OK;
}
#ifdef USE_MFG_FW
const char* kFirmwareVer =
"wl0: Oct 5 2018 04:50:34 version 7.45.96.34 (r783496 WLTEST) FWID 01-df2d9ead";
#else
const char* kFirmwareVer = "wl0: Sep 10 2018 16:37:38 version 7.35.79 (r487924) FWID 01-c76ab99a";
#endif
const char* kFirmwareCap =
"ap sta wme 802.11d 802.11h rm cqa cac dualband ampdu ampdu_tx ampdu_rx amsdurx tdls "
"radio_pwrsave btamp p2p proptxstatus mchan wds dwds p2po anqpo vht-prop-rates dfrts "
"txpwrcache stbc-tx stbc-rx-1ss epno pfnx wnm bsstrans mfp ndoe rssi_mon cptlv-4 extsae";
zx_status_t SimFirmware::IovarsGet(uint16_t ifidx, const char* name, void* value_out,
size_t value_len, bcme_status_t* fw_err) {
zx_status_t status;
if (fw_err == nullptr) {
BRCMF_ERR(
"fw_err cannot be nullptr for IovarsGet() calls. IovarsGet() should not be called directly "
"from firmware simulator tests.");
return ZX_ERR_INTERNAL;
} else {
*fw_err = BCME_OK;
}
const std::vector<uint8_t>* err_inj_alt_value;
if (err_inj_.CheckIfErrInjIovarEnabled(name, &status, fw_err, &err_inj_alt_value, ifidx)) {
if (err_inj_alt_value != nullptr) {
// Use provided replacement data
size_t alt_data_size = err_inj_alt_value->size();
memcpy(value_out, err_inj_alt_value->data(), std::min(alt_data_size, value_len));
if (alt_data_size < value_len) {
// Zero out any remaining bytes
uint8_t* bytes_out = reinterpret_cast<uint8_t*>(value_out);
memset(&bytes_out[alt_data_size], 0, value_len - alt_data_size);
}
} else {
memset(value_out, 0, value_len);
}
return status;
}
if (!std::strcmp(name, "arp_ol")) {
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_INVALID_ARGS;
}
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(value_out, &iface_tbl_[ifidx].arp_ol, sizeof(uint32_t));
} else if (!std::strcmp(name, "arpoe")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(value_out, &iface_tbl_[ifidx].arpoe, sizeof(uint32_t));
} else if (!std::strcmp(name, "ver")) {
if (value_len >= (strlen(kFirmwareVer) + 1)) {
strlcpy(static_cast<char*>(value_out), kFirmwareVer, value_len);
} else {
return ZX_ERR_INVALID_ARGS;
}
} else if (!std::strcmp(name, "country")) {
if (value_len >= (sizeof(brcmf_fil_country_le))) {
memcpy(value_out, &country_code_, sizeof(brcmf_fil_country_le));
} else {
return ZX_ERR_INVALID_ARGS;
}
} else if (!std::strcmp(name, "cur_etheraddr")) {
if (value_len < ETH_ALEN) {
return ZX_ERR_INVALID_ARGS;
} else {
// Return mac address of iface if set else return the system mac address
if (iface_tbl_[ifidx].mac_addr_set)
memcpy(value_out, iface_tbl_[ifidx].mac_addr.byte, ETH_ALEN);
else
memcpy(value_out, mac_addr_.data(), ETH_ALEN);
}
} else if (!std::strcmp(name, "pfn_macaddr")) {
if (value_len < ETH_ALEN) {
return ZX_ERR_INVALID_ARGS;
} else {
memcpy(value_out, pfn_mac_addr_.byte, ETH_ALEN);
}
} else if (!std::strcmp(name, "assoc_retry_max")) {
if (value_len < sizeof(assoc_max_retries_)) {
return ZX_ERR_INVALID_ARGS;
} else {
memcpy(value_out, &assoc_max_retries_, sizeof(assoc_max_retries_));
}
} else if (!std::strcmp(name, "mpc")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
} else {
memcpy(value_out, &mpc_, sizeof(uint32_t));
}
} else if (!std::strcmp(name, "wsec")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(value_out, &iface_tbl_[ifidx].wsec, sizeof(uint32_t));
} else if (!std::strcmp(name, "wpa_auth")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(value_out, &iface_tbl_[ifidx].wpa_auth, sizeof(uint32_t));
} else if (!std::strcmp(name, "auth")) {
if (value_len < sizeof(iface_tbl_[ifidx].auth_type)) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(value_out, &iface_tbl_[ifidx].auth_type, sizeof(iface_tbl_[ifidx].auth_type));
} else if (!std::strcmp(name, "wsec_key")) {
if (value_len < sizeof(brcmf_wsec_key_le)) {
return ZX_ERR_INVALID_ARGS;
}
std::vector<brcmf_wsec_key_le>& key_list = iface_tbl_[ifidx].wsec_key_list;
auto key_iter = std::find_if(key_list.begin(), key_list.end(), [=](brcmf_wsec_key_le& k) {
return k.index == iface_tbl_[ifidx].cur_key_idx;
});
if (key_iter == key_list.end()) {
return ZX_ERR_NOT_FOUND;
}
memcpy(value_out, &(*key_iter), sizeof(brcmf_wsec_key_le));
} else if (!std::strcmp(name, "chanspec")) {
if (value_len < sizeof(uint16_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_BAD_STATE;
}
memcpy(value_out, &iface_tbl_[ifidx].chanspec, sizeof(uint16_t));
} else if (!std::strcmp(name, "rxchain")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_BAD_STATE;
}
// rxchain 1 indicates antenna index 0.
const uint32_t sim_rxchain = 1;
memcpy(value_out, &sim_rxchain, sizeof(uint32_t));
} else if (!std::strcmp(name, "snr")) {
if (value_len < sizeof(int32_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_BAD_STATE;
}
int32_t sim_snr = 40;
memcpy(value_out, &sim_snr, sizeof(sim_snr));
} else if (!std::strcmp(name, "tlv")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_BAD_STATE;
}
memcpy(value_out, &iface_tbl_[ifidx].tlv, sizeof(uint32_t));
} else if (!std::strcmp(name, "wstats_counters")) {
if (value_len < sizeof(wl_wstats_cnt_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_BAD_STATE;
}
wl_wstats_cnt_t wstats_cnt = {
.version = WSTATS_CNT_T_VERSION,
};
// Manually populate a few histograms.
const uint16_t snr_db = 60;
const uint32_t snr_num_frames = 50;
wstats_cnt.rxsnr[snr_db] = snr_num_frames;
const uint32_t noiseflr_dbm_index = 170;
const uint32_t noiseflr_num_frames = 20;
wstats_cnt.rxnoiseflr[noiseflr_dbm_index] = noiseflr_num_frames;
const uint32_t rssi_dbm_index = 190;
const uint32_t rssi_num_frames = 40;
wstats_cnt.rxrssi[rssi_dbm_index] = rssi_num_frames;
const uint32_t rate_index = 7;
const uint32_t rate_num_frames = 80;
wstats_cnt.rx11g[rate_index] = rate_num_frames;
memcpy(value_out, &wstats_cnt, sizeof(wl_wstats_cnt_t));
} else if (!std::strcmp(name, "assoc_info")) {
if (value_len < sizeof(struct brcmf_cfg80211_assoc_ielen_le)) {
return ZX_ERR_INVALID_ARGS;
}
((struct brcmf_cfg80211_assoc_ielen_le*)value_out)->req_len = 0;
((struct brcmf_cfg80211_assoc_ielen_le*)value_out)->resp_len = assoc_resp_ies_len_;
} else if (!std::strcmp(name, "assoc_resp_ies")) {
if (value_len < assoc_resp_ies_len_) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(value_out, assoc_resp_ies_, assoc_resp_ies_len_);
} else if (!std::strcmp(name, "cap")) {
// TODO: Provide means to simulate hardware with different capabilities.
if (value_len < strlen(kFirmwareCap) + 1) {
*fw_err = BCME_BUFTOOSHORT;
return ZX_OK;
}
strlcpy(static_cast<char*>(value_out), kFirmwareCap, value_len);
} else if (!std::strcmp(name, "nmode")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
// TODO: Provide means to simulate hardware without nmode.
uint32_t* result_ptr = static_cast<uint32_t*>(value_out);
*result_ptr = 1;
} else if (!std::strcmp(name, "vhtmode")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
// TODO: Provide means to simulate hardware without vhtmode.
uint32_t* result_ptr = static_cast<uint32_t*>(value_out);
*result_ptr = 1;
} else if (!std::strcmp(name, "rrm")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
// TODO: Provide means to simulate hardware without rrm.
uint32_t* result_ptr = static_cast<uint32_t*>(value_out);
*result_ptr = 1;
} else if (!std::strcmp(name, "allmulti")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (!iface_tbl_[ifidx].allocated) {
return ZX_ERR_BAD_STATE;
}
uint32_t* result_ptr = static_cast<uint32_t*>(value_out);
*result_ptr = iface_tbl_[ifidx].allmulti;
} else if (!std::strcmp(name, "wme_ac_sta")) {
if (value_len < sizeof(edcf_acparam_t) * 4) {
return ZX_ERR_INVALID_ARGS;
}
// Note: the below mocked AC parameters are slightly different from default values
// because we set different values for each AC to make sure in the test that
// the right ACs are parsed.
edcf_acparam_t params[4];
// AC_BE
params[0].aci = 4; // aifsn
params[0].ecw = 5 + (10 << 4); // ecw_min + (ecw_max << 4)
params[0].txop = 0;
// AC_BK
params[1].aci = 7 + (1 << 5); // aifsn + (aci << 5)
params[1].ecw = 6 + (11 << 4); // ecw_min + (ecw_max << 4)
params[1].txop = 0;
// AC_VI
params[2].aci = 3 + (2 << 5); // aifsn + (aci << 5)
params[2].ecw = 4 + (5 << 4); // ecw_min + (ecw_max << 4)
params[2].txop = 94;
// AC_VO
params[3].aci = 2 + (1 << 4) + (3 << 5); // aifsn + (acm << 4) + (aci << 5)
params[3].ecw = 2 + (4 << 4); // ecw_min + (ecw_max << 4)
params[3].txop = 47;
memcpy(value_out, params, sizeof(params));
} else if (!std::strcmp(name, "wme_apsd")) {
if (value_len < sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
uint32_t* result_ptr = static_cast<uint32_t*>(value_out);
*result_ptr = 1;
} else {
// FIXME: We should return an error for an unrecognized firmware variable
BRCMF_DBG(SIM, "Ignoring request to read iovar '%s'", name);
memset(value_out, 0, value_len);
}
return ZX_OK;
}
// If setting for the first time, save it as system mac address as well
zx_status_t SimFirmware::SetMacAddr(uint16_t ifidx, const uint8_t* mac_addr) {
if (mac_addr_set_ == false) {
BRCMF_DBG(SIM, "Setting system mac addr: " MAC_FMT_STR, MAC_FMT_ARGS(mac_addr));
memcpy(mac_addr_.data(), mac_addr, ETH_ALEN);
memcpy(pfn_mac_addr_.byte, mac_addr, ETH_ALEN);
mac_addr_set_ = true;
}
memcpy(iface_tbl_[ifidx].mac_addr.byte, mac_addr, ETH_ALEN);
iface_tbl_[ifidx].mac_addr_set = true;
BRCMF_DBG(SIM, "Setting mac addr ifidx: %d: " MAC_FMT_STR, ifidx, MAC_FMT_ARGS(mac_addr));
return ZX_OK;
}
common::MacAddr SimFirmware::GetMacAddr(uint16_t ifidx) {
if (ifidx < kMaxIfSupported && iface_tbl_[ifidx].allocated && iface_tbl_[ifidx].mac_addr_set) {
return iface_tbl_[ifidx].mac_addr;
}
ZX_ASSERT(mac_addr_set_);
return common::MacAddr(mac_addr_);
}
zx_status_t SimFirmware::ScanStart(std::unique_ptr<ScanOpts> opts) {
if (scan_state_.state != ScanState::STOPPED) {
// Can't start a scan while another is in progress
return ZX_ERR_NOT_SUPPORTED;
}
if (!iface_tbl_[kClientIfidx].allocated) {
BRCMF_ERR("client iface has not been allocated.");
return ZX_ERR_BAD_STATE;
}
// I believe in real firmware this will just search all channels. We need to define that set in
// order for this functionality to work.
ZX_ASSERT_MSG(opts->channels.size() >= 1,
"No channels provided to escan start request - unsupported");
// Configure state
scan_state_.state = ScanState::SCANNING;
scan_state_.opts = std::move(opts);
scan_state_.channel_index = 0;
// Start scan
uint16_t chanspec = scan_state_.opts->channels[scan_state_.channel_index++];
wlan_channel_t channel;
chanspec_to_channel(&d11_inf_, chanspec, &channel);
hw_.SetChannel(channel);
// Do an active scan using random mac
if (scan_state_.opts->is_active) {
scan_state_.active_scan_attempts = 1;
simulation::SimProbeReqFrame probe_req_frame(pfn_mac_addr_);
hw_.Tx(probe_req_frame);
}
hw_.EnableRx();
hw_.RequestCallback(std::bind(&SimFirmware::ScanContinue, this),
scan_state_.opts->dwell_time);
return ZX_OK;
}
// Continue or stop scanning based on scan type.
// Active scan - continue with current channel until max attempts of probes sent
// or else switch to next channel
// Passive scan - switch to next channel
void SimFirmware::ScanContinue() {
switch (scan_state_.state) {
case ScanState::STOPPED:
// We may see this event if a scan was cancelled -- just ignore it
return;
case ScanState::HOME:
// We don't yet support intermittent scanning
return;
case ScanState::SCANNING:
// If active scan, check and resend probe request if max attempts not reached.
if (scan_state_.opts->is_active &&
scan_state_.active_scan_attempts < scan_state_.opts->active_scan_max_attempts) {
BRCMF_DBG(SIM, "active scan continue: %d att: %d max: %d", scan_state_.opts->is_active,
scan_state_.active_scan_attempts, scan_state_.opts->active_scan_max_attempts);
// Note that the channel remains the same
scan_state_.active_scan_attempts++;
simulation::SimProbeReqFrame probe_req_frame(pfn_mac_addr_);
hw_.Tx(probe_req_frame);
hw_.RequestCallback(std::bind(&SimFirmware::ScanContinue, this),
scan_state_.opts->dwell_time);
return;
}
if (scan_state_.channel_index >= scan_state_.opts->channels.size()) {
ScanComplete(BRCMF_E_STATUS_SUCCESS);
} else {
// Scan next channel
uint16_t chanspec = scan_state_.opts->channels[scan_state_.channel_index++];
wlan_channel_t channel;
chanspec_to_channel(&d11_inf_, chanspec, &channel);
hw_.SetChannel(channel);
BRCMF_DBG(SIM, "Continue scan - next chanspec: 0x%x", chanspec);
if (scan_state_.opts->is_active) {
scan_state_.active_scan_attempts = 1;
simulation::SimProbeReqFrame probe_req_frame(pfn_mac_addr_);
hw_.Tx(probe_req_frame);
}
hw_.RequestCallback(std::bind(&SimFirmware::ScanContinue, this),
scan_state_.opts->dwell_time);
}
}
}
// Clean up state after a scan request is finished
void SimFirmware::ScanComplete(brcmf_fweh_event_status_t status) {
if (scan_state_.opts->is_active) {
BRCMF_DBG(SIM, "Resetting pfn_mac_addr_ to system mac addr: " MAC_FMT_STR,
MAC_FMT_ARGS(mac_addr_.data()));
memcpy(pfn_mac_addr_.byte, mac_addr_.data(), ETH_ALEN);
}
hw_.DisableRx();
scan_state_.state = ScanState::STOPPED;
// Restore the operating channel since Scan is done. This applies
// only if the scan was started when the IF is already associated
if (iface_tbl_[kClientIfidx].chanspec) {
wlan_channel_t channel;
chanspec_to_channel(&d11_inf_, iface_tbl_[kClientIfidx].chanspec, &channel);
hw_.SetChannel(channel);
}
scan_state_.opts->on_done_fn(status);
scan_state_.opts = nullptr;
}
// Send an event to the firmware notifying them that the scan has completed.
zx_status_t SimFirmware::HandleEscanRequest(const brcmf_escan_params_le* escan_params,
size_t params_len) {
if (escan_params->version != BRCMF_ESCAN_REQ_VERSION) {
BRCMF_DBG(SIM, "Mismatched escan version (expected %d, saw %d) - ignoring request",
BRCMF_ESCAN_REQ_VERSION, escan_params->version);
return ZX_ERR_NOT_SUPPORTED;
}
switch (escan_params->action) {
case WL_ESCAN_ACTION_START:
return EscanStart(escan_params->sync_id, &escan_params->params_le,
params_len - offsetof(brcmf_escan_params_le, params_le));
case WL_ESCAN_ACTION_CONTINUE:
ZX_ASSERT_MSG(0, "Unimplemented escan option WL_ESCAN_ACTION_CONTINUE");
return ZX_ERR_NOT_SUPPORTED;
case WL_ESCAN_ACTION_ABORT:
ZX_ASSERT_MSG(0, "Unimplemented escan option WL_ESCAN_ACTION_ABORT");
return ZX_ERR_NOT_SUPPORTED;
default:
ZX_ASSERT_MSG(0, "Unrecognized escan option %d", escan_params->action);
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
// When asked to start an escan, we will listen on each of the specified channels for the requested
// duration (dwell time). We accomplish this by setting up a future event for the next channel,
// iterating until we have scanned all channels.
zx_status_t SimFirmware::EscanStart(uint16_t sync_id, const brcmf_scan_params_le* params,
size_t params_len) {
auto scan_opts = std::make_unique<ScanOpts>();
scan_opts->sync_id = sync_id;
switch (params->scan_type) {
case BRCMF_SCANTYPE_ACTIVE:
scan_opts->is_active = true;
if (params->active_time == static_cast<uint32_t>(-1)) {
BRCMF_ERR("No active scan time in parameter");
return ZX_ERR_INVALID_ARGS;
} else {
int32_t nprobes = (int32_t)params->nprobes;
scan_opts->active_scan_max_attempts = nprobes <= 0 ? 1 : nprobes;
// Dwell time is equally split across the # of attempts
scan_opts->dwell_time = zx::msec(params->active_time) / scan_opts->active_scan_max_attempts;
BRCMF_DBG(SIM, "Active scan req: act time: %u nprobes: %d act time: %d dwell: %zu",
params->active_time, params->nprobes, params->active_time,
scan_opts->dwell_time.get());
}
break;
case BRCMF_SCANTYPE_PASSIVE:
scan_opts->is_active = false;
// Determine dwell time. If specified in the request, use that value. Otherwise, if a default
// dwell time has been specified, use that value. Otherwise, fail.
if (params->passive_time == static_cast<uint32_t>(-1)) {
if (default_passive_time_ == static_cast<uint32_t>(-1)) {
BRCMF_ERR("Attempt to use default passive time, iovar hasn't been set yet");
return ZX_ERR_INVALID_ARGS;
}
scan_opts->dwell_time = zx::msec(default_passive_time_);
} else {
scan_opts->dwell_time = zx::msec(params->passive_time);
}
break;
default:
BRCMF_DBG(SIM, "Invalid scan type requested: %d", params->scan_type);
return ZX_ERR_INVALID_ARGS;
}
size_t num_channels = params->channel_num & BRCMF_SCAN_PARAMS_COUNT_MASK;
// Configure state
scan_opts->channels.resize(num_channels);
std::copy(&params->channel_list[0], &params->channel_list[num_channels],
scan_opts->channels.data());
scan_opts->on_result_fn = std::bind(&SimFirmware::EscanResultSeen, this, std::placeholders::_1);
scan_opts->on_done_fn = std::bind(&SimFirmware::EscanComplete, this, std::placeholders::_1);
return ScanStart(std::move(scan_opts));
}
void SimFirmware::EscanComplete(brcmf_fweh_event_status_t event_status) {
// The last field in a brcmf_escan_result_le is variable-length (although it isn't defined that
// way). To generate a scan complete message, it has to have no BSS entries.
size_t buf_len = offsetof(brcmf_escan_result_le, bss_info_le);
auto buf = std::make_unique<std::vector<uint8_t>>(buf_len);
// Assemble the result buffer. As per above, we are making a pointer to the escan_result, but we
// MUST NOT reference the last field (bss_info_le), since it hasn't been allocated.
auto result = reinterpret_cast<brcmf_escan_result_le*>(buf->data());
result->buflen = buf_len;
result->version = BRCMF_BSS_INFO_VERSION;
result->sync_id = scan_state_.opts->sync_id;
result->bss_count = 0;
SendEventToDriver(buf_len, std::move(buf), BRCMF_E_ESCAN_RESULT, event_status, kClientIfidx);
}
void SimFirmware::Rx(std::shared_ptr<const simulation::SimFrame> frame,
std::shared_ptr<const simulation::WlanRxInfo> info) {
if (frame->FrameType() == simulation::SimFrame::FRAME_TYPE_MGMT) {
auto mgmt_frame = std::static_pointer_cast<const simulation::SimManagementFrame>(frame);
RxMgmtFrame(mgmt_frame, info);
} else if (frame->FrameType() == simulation::SimFrame::FRAME_TYPE_DATA) {
auto data_frame = std::static_pointer_cast<const simulation::SimDataFrame>(frame);
RxDataFrame(data_frame, info);
}
}
void SimFirmware::RxMgmtFrame(std::shared_ptr<const simulation::SimManagementFrame> mgmt_frame,
std::shared_ptr<const simulation::WlanRxInfo> info) {
switch (mgmt_frame->MgmtFrameType()) {
case simulation::SimManagementFrame::FRAME_TYPE_BEACON: {
auto beacon = std::static_pointer_cast<const simulation::SimBeaconFrame>(mgmt_frame);
RxBeacon(info->channel, beacon, info->signal_strength);
break;
}
case simulation::SimManagementFrame::FRAME_TYPE_PROBE_RESP: {
auto probe_resp = std::static_pointer_cast<const simulation::SimProbeRespFrame>(mgmt_frame);
RxProbeResp(info->channel, probe_resp, info->signal_strength);
break;
}
case simulation::SimManagementFrame::FRAME_TYPE_ASSOC_REQ: {
auto assoc_req = std::static_pointer_cast<const simulation::SimAssocReqFrame>(mgmt_frame);
RxAssocReq(assoc_req);
break;
}
case simulation::SimManagementFrame::FRAME_TYPE_ASSOC_RESP: {
auto assoc_resp = std::static_pointer_cast<const simulation::SimAssocRespFrame>(mgmt_frame);
RxAssocResp(assoc_resp);
break;
}
case simulation::SimManagementFrame::FRAME_TYPE_DISASSOC_REQ: {
auto disassoc_req =
std::static_pointer_cast<const simulation::SimDisassocReqFrame>(mgmt_frame);
RxDisassocReq(disassoc_req);
break;
}
case simulation::SimManagementFrame::FRAME_TYPE_AUTH: {
auto auth_resp = std::static_pointer_cast<const simulation::SimAuthFrame>(mgmt_frame);
RxAuthFrame(auth_resp);
break;
}
case simulation::SimManagementFrame::FRAME_TYPE_DEAUTH: {
auto deauth_req = std::static_pointer_cast<const simulation::SimDeauthFrame>(mgmt_frame);
RxDeauthReq(deauth_req);
break;
}
default:
break;
}
}
bool SimFirmware::OffloadArpFrame(int16_t ifidx,
std::shared_ptr<const simulation::SimDataFrame> data_frame) {
// Feature is disabled for this interface
if (iface_tbl_[ifidx].arpoe == 0) {
return false;
}
if (data_frame->payload_.size() < (sizeof(ethhdr) + sizeof(ether_arp))) {
return false;
}
auto eth_hdr = reinterpret_cast<const ethhdr*>(data_frame->payload_.data());
if (ntohs(eth_hdr->h_proto) != ETH_P_ARP) {
return false;
}
auto arp_hdr = reinterpret_cast<const ether_arp*>(&data_frame->payload_.data()[sizeof(eth_hdr)]);
uint16_t ar_op = ntohs(arp_hdr->ea_hdr.ar_op);
uint32_t arp_ol = iface_tbl_[ifidx].arp_ol;
if (ar_op == ARPOP_REQUEST) {
// TODO: Actually construct the ARP reply, which would require us to sniff for IP addresses.
// For now, not forwarding the packet to the host is enough.
return (arp_ol & BRCMF_ARP_OL_AGENT) && (arp_ol & BRCMF_ARP_OL_PEER_AUTO_REPLY);
}
// TODO: Add support for ARP offloading of other commands
ZX_ASSERT_MSG(0, "Support for ARP offloading (op = %d) unimplemented", ar_op);
return false;
}
void SimFirmware::RxDataFrame(std::shared_ptr<const simulation::SimDataFrame> data_frame,
std::shared_ptr<const simulation::WlanRxInfo> info) {
bool is_broadcast = (data_frame->addr1_ == common::kBcastMac);
for (uint8_t idx = 0; idx < kMaxIfSupported; idx++) {
if (!iface_tbl_[idx].allocated)
continue;
if (!(is_broadcast || (data_frame->addr1_ == iface_tbl_[idx].mac_addr)))
continue;
if (OffloadArpFrame(idx, data_frame))
continue;
if (idx == kClientIfidx && assoc_state_.state != AssocState::ASSOCIATED) {
// Receiving a unicast packet before client is associated. Send a deauth event
// to driver (in the real world we would have sent a deauth frame to the sender
BRCMF_DBG(SIM, "Sending E_DEAUTH to driver");
SendEventToDriver(0, nullptr, BRCMF_E_DEAUTH, BRCMF_E_STATUS_SUCCESS, idx, nullptr, 0,
BRCMF_E_REASON_UCAST_FROM_UNASSOC_STA);
continue;
}
SendFrameToDriver(idx, data_frame->payload_.size(), data_frame->payload_, info);
}
}
// Start or restart the beacon watchdog. This is a timeout event mirroring how the firmware can
// detect when a connection is lost from the lack of beacons received.
void SimFirmware::RestartBeaconWatchdog() {
DisableBeaconWatchdog();
assoc_state_.is_beacon_watchdog_active = true;
hw_.RequestCallback(std::bind(&SimFirmware::HandleBeaconTimeout, this), beacon_timeout_,
&assoc_state_.beacon_watchdog_id_);
}
void SimFirmware::DisableBeaconWatchdog() {
if (assoc_state_.is_beacon_watchdog_active) {
hw_.CancelCallback(assoc_state_.beacon_watchdog_id_);
}
}
void SimFirmware::HandleBeaconTimeout() {
// Ignore if we are not associated
if (assoc_state_.state != AssocState::ASSOCIATED) {
return;
}
assoc_state_.is_beacon_watchdog_active = false;
// Indicate to the driver that we're disassociating due to lost beacons
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_SUCCESS, kClientIfidx, 0,
BRCMF_E_REASON_LOW_RSSI);
SendEventToDriver(0, nullptr, BRCMF_E_LINK, BRCMF_E_STATUS_SUCCESS, kClientIfidx, 0,
BRCMF_E_REASON_DEAUTH);
AssocClearContext();
}
void SimFirmware::ConductChannelSwitch(const wlan_channel_t& dst_channel, uint8_t mode) {
// Change fw and hw channel
uint16_t chanspec;
ZX_ASSERT_MSG(iface_tbl_[kClientIfidx].allocated, "No client found!");
hw_.SetChannel(dst_channel);
chanspec = channel_to_chanspec(&d11_inf_, &dst_channel);
SetIFChanspec(kClientIfidx, chanspec);
// Send up CSA event to driver
auto buf = std::make_unique<std::vector<uint8_t>>(sizeof(uint8_t));
*(buf->data()) = mode;
SendEventToDriver(sizeof(uint8_t), std::move(buf), BRCMF_E_CSA_COMPLETE_IND,
BRCMF_E_STATUS_SUCCESS, kClientIfidx);
// Clear state
channel_switch_state_.state = ChannelSwitchState::HOME;
}
// static
int8_t SimFirmware::RssiDbmFromSignalStrength(double signal_strength) {
// truncate signal strength to rssi unit
if (signal_strength > INT8_MAX) {
return INT8_MAX;
} else if (signal_strength < INT8_MIN) {
return INT8_MIN;
}
return signal_strength;
}
void SimFirmware::RxBeacon(const wlan_channel_t& channel,
std::shared_ptr<const simulation::SimBeaconFrame> frame,
double signal_strength) {
if (scan_state_.state == ScanState::SCANNING && !scan_state_.opts->is_active) {
int8_t rssi_dbm = RssiDbmFromSignalStrength(signal_strength);
ScanResult scan_result = {
.channel = channel, .bssid = frame->bssid_, .rssi_dbm = rssi_dbm, .ies = frame->IEs_};
scan_result.bss_capability.set_val(frame->capability_info_.val());
scan_state_.opts->on_result_fn(scan_result);
// TODO(fxbug.dev/49350): Channel switch during scanning need to be supported.
} else if (assoc_state_.state == AssocState::ASSOCIATED &&
frame->bssid_ == assoc_state_.opts->bssid) {
// if we're associated with this AP, start/restart the beacon watchdog
RestartBeaconWatchdog();
auto ie = frame->FindIe(simulation::InformationElement::IE_TYPE_CSA);
if (ie == nullptr) {
return;
}
// If CSA IE exists.
auto csa_ie = std::static_pointer_cast<simulation::CsaInformationElement>(ie);
// Get current chanspec of client ifidx and convert to channel.
wlan_channel_t channel = GetIfChannel(false);
zx::duration SwitchDelay = frame->interval_ * (int64_t)csa_ie->channel_switch_count_;
if (channel_switch_state_.state == ChannelSwitchState::HOME) {
// If the destination channel is the same as current channel, just ignore it.
if (csa_ie->new_channel_number_ == channel.primary) {
return;
}
channel.primary = csa_ie->new_channel_number_;
channel_switch_state_.new_channel = csa_ie->new_channel_number_;
channel_switch_state_.state = ChannelSwitchState::SWITCHING;
} else {
ZX_ASSERT(channel_switch_state_.state == ChannelSwitchState::SWITCHING);
if (csa_ie->new_channel_number_ == channel_switch_state_.new_channel) {
return;
}
// If the new channel is different from the previous dst channel, cancel callback.
hw_.CancelCallback(channel_switch_state_.switch_timer_id);
// If it's the same as current channel for this client before switching, just simply cancel
// the switch event and clear state.
if (csa_ie->new_channel_number_ == channel.primary) {
channel_switch_state_.state = ChannelSwitchState::HOME;
return;
}
// Schedule a new event when dst channel change.
channel.primary = csa_ie->new_channel_number_;
}
hw_.RequestCallback(
std::bind(&SimFirmware::ConductChannelSwitch, this, channel, csa_ie->channel_switch_mode_),
SwitchDelay, &channel_switch_state_.switch_timer_id);
}
}
void SimFirmware::RxProbeResp(const wlan_channel_t& channel,
std::shared_ptr<const simulation::SimProbeRespFrame> frame,
double signal_strength) {
if (scan_state_.state != ScanState::SCANNING || !scan_state_.opts->is_active) {
return;
}
int8_t rssi_dbm = SimFirmware::RssiDbmFromSignalStrength(signal_strength);
ScanResult scan_result = {
.channel = channel, .bssid = frame->src_addr_, .rssi_dbm = rssi_dbm, .ies = frame->IEs_};
scan_result.bss_capability.set_val(frame->capability_info_.val());
scan_state_.opts->on_result_fn(scan_result);
}
// Handle an Rx Beacon sent to us from the hardware, using it to fill in all of the fields in a
// brcmf_escan_result.
void SimFirmware::EscanResultSeen(const ScanResult& result_in) {
std::vector<uint8_t> ie_buf;
for (const auto& ie : result_in.ies) {
if (ie == nullptr) {
continue;
}
switch (ie->IeType()) {
case simulation::InformationElement::IE_TYPE_SSID: {
const auto ssid_ie = std::static_pointer_cast<simulation::SsidInformationElement>(ie);
std::vector<uint8_t> current_ie_buf = ssid_ie->ToRawIe();
ie_buf.insert(ie_buf.end(), current_ie_buf.begin(), current_ie_buf.end());
break;
}
case simulation::InformationElement::IE_TYPE_CSA: {
const auto csa_ie = std::static_pointer_cast<simulation::CsaInformationElement>(ie);
std::vector<uint8_t> current_ie_buf = csa_ie->ToRawIe();
ie_buf.insert(ie_buf.end(), current_ie_buf.begin(), current_ie_buf.end());
break;
}
case simulation::InformationElement::IE_TYPE_WPA1:
__FALLTHROUGH;
case simulation::InformationElement::IE_TYPE_WPA2:
__FALLTHROUGH;
default:
break;
}
}
// scan_result_size includes all BSS info structures (each including IEs). Like the firmware, we
// only send one result back at a time.
size_t scan_result_size = roundup(sizeof(brcmf_escan_result_le) + ie_buf.size(), 4);
auto buf = std::make_unique<std::vector<uint8_t>>(scan_result_size);
uint8_t* buffer_data = buf->data();
auto result_out = reinterpret_cast<brcmf_escan_result_le*>(buffer_data);
result_out->buflen = scan_result_size;
result_out->version = BRCMF_BSS_INFO_VERSION;
result_out->sync_id = scan_state_.opts->sync_id;
result_out->bss_count = 1;
struct brcmf_bss_info_le* bss_info = &result_out->bss_info_le;
bss_info->version = BRCMF_BSS_INFO_VERSION;
// length of this record (includes IEs)
bss_info->length = roundup(sizeof(brcmf_bss_info_le) + ie_buf.size(), 4);
// channel
bss_info->chanspec = channel_to_chanspec(&d11_inf_, &result_in.channel);
// capability
bss_info->capability = result_in.bss_capability.val();
// ssid
bss_info->SSID_len = 0; // SSID will go into an IE
// bssid
ZX_ASSERT(sizeof(bss_info->BSSID) == common::kMacAddrLen);
memcpy(bss_info->BSSID, result_in.bssid.byte, common::kMacAddrLen);
// RSSI
bss_info->RSSI = result_in.rssi_dbm;
// IEs
bss_info->ie_offset = sizeof(brcmf_bss_info_le);
size_t ie_offset = sizeof(brcmf_escan_result_le);
uint8_t* ie_data = &buffer_data[ie_offset];
std::memcpy(ie_data, ie_buf.data(), ie_buf.size());
bss_info->ie_length = ie_buf.size();
// Wrap this in an event and send it back to the driver
SendEventToDriver(scan_result_size, std::move(buf), BRCMF_E_ESCAN_RESULT, BRCMF_E_STATUS_PARTIAL,
kClientIfidx);
}
std::shared_ptr<std::vector<uint8_t>> SimFirmware::CreateEventBuffer(
size_t requested_size, brcmf_event_msg_be** msg_out_be, size_t* payload_offset_out) {
size_t total_size = sizeof(brcmf_event) + requested_size;
size_t event_data_offset;
// Note: events always encode the interface index into the event header and 0 into the BCDC
// header.
auto buf = CreateBcdcBuffer(0, total_size, &event_data_offset);
uint8_t* buffer_data = buf->data();
auto event = reinterpret_cast<brcmf_event*>(&buffer_data[event_data_offset]);
memcpy(event->eth.h_dest, mac_addr_.data(), ETH_ALEN);
memcpy(event->eth.h_source, mac_addr_.data(), ETH_ALEN);
// Disable local bit - we do this because, well, the real firmware does this.
event->eth.h_source[0] &= ~0x2;
event->eth.h_proto = htobe16(ETH_P_LINK_CTL);
auto hdr_be = &event->hdr;
// hdr_be->subtype unused
hdr_be->length = htobe16(total_size);
hdr_be->version = 0;
memcpy(&hdr_be->oui, BRCM_OUI, sizeof(hdr_be->oui));
hdr_be->usr_subtype = htobe16(BCMILCP_BCM_SUBTYPE_EVENT);
// Set the generic fields of the event msg
*msg_out_be = &event->msg;
(*msg_out_be)->version = htobe16(2);
(*msg_out_be)->datalen = htobe32(requested_size);
memcpy((*msg_out_be)->addr, mac_addr_.data(), ETH_ALEN);
memcpy((*msg_out_be)->ifname, kDefaultIfcName, strlen(kDefaultIfcName));
// Payload immediately follows the brcmf_event structure
if (payload_offset_out != nullptr) {
*payload_offset_out = event_data_offset + sizeof(brcmf_event);
}
return buf;
}
void SimFirmware::SendEventToDriver(size_t payload_size,
std::shared_ptr<std::vector<uint8_t>> buffer_in,
uint32_t event_type, uint32_t status, uint16_t ifidx,
char* ifname, uint16_t flags, uint32_t reason,
std::optional<common::MacAddr> addr,
std::optional<zx::duration> delay) {
brcmf_event_msg_be* msg_be;
size_t payload_offset;
// Assert if ifidx is not valid
if (event_type != BRCMF_E_IF)
ZX_ASSERT(ifidx < kMaxIfSupported && iface_tbl_[ifidx].allocated);
auto buf = CreateEventBuffer(payload_size, &msg_be, &payload_offset);
msg_be->flags = htobe16(flags);
msg_be->event_type = htobe32(event_type);
msg_be->status = htobe32(status);
msg_be->reason = htobe32(reason);
msg_be->auth_type = htobe32(iface_tbl_[ifidx].auth_type);
msg_be->ifidx = ifidx;
msg_be->bsscfgidx = iface_tbl_[ifidx].bsscfgidx;
if (ifname)
memcpy(msg_be->ifname, ifname, IFNAMSIZ);
if (addr)
memcpy(msg_be->addr, addr->byte, ETH_ALEN);
if (payload_size != 0) {
ZX_ASSERT(buffer_in != nullptr);
uint8_t* buf_data = buf->data();
memcpy(&buf_data[payload_offset], buffer_in->data(), payload_size);
}
if (delay && delay->get() > 0) {
// Setup the callback and return.
hw_.RequestCallback(std::bind(&brcmf_sim_rx_event, simdev_, buf),
delay.value());
return;
} else {
BRCMF_DBG(SIM, "Sending Event: %d", event_type);
brcmf_sim_rx_event(simdev_, std::move(buf));
}
}
void SimFirmware::SendFrameToDriver(uint16_t ifidx, size_t payload_size,
const std::vector<uint8_t>& buffer_in,
std::shared_ptr<const simulation::WlanRxInfo> info) {
size_t header_offset;
auto buf = CreateBcdcBuffer(ifidx, payload_size, &header_offset);
if (payload_size != 0) {
ZX_ASSERT(!buffer_in.empty());
uint8_t* buf_data = buf->data();
memcpy(&buf_data[header_offset], buffer_in.data(), payload_size);
}
brcmf_sim_rx_frame(simdev_, std::move(buf));
}
void SimFirmware::convert_chanspec_to_channel(uint16_t chanspec, wlan_channel_t* channel) {
chanspec_to_channel(&d11_inf_, chanspec, channel);
}
uint16_t SimFirmware::convert_channel_to_chanspec(wlan_channel_t* channel) {
return channel_to_chanspec(&d11_inf_, channel);
}
} // namespace wlan::brcmfmac