blob: ec6583d4e4332150e7414b90dea8eeeda0138636 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fidl/fuchsia.wlan.ieee80211/cpp/wire_types.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/wire/arena.h>
#include <lib/sync/cpp/completion.h>
#include <lib/zx/channel.h>
#include <stdlib.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <iterator>
#include <list>
#include <memory>
#include <utility>
#include <gtest/gtest.h>
#include "lib/fidl/cpp/wire/array.h"
#include "third_party/iwlwifi/platform/banjo/common.h"
#include "third_party/iwlwifi/platform/banjo/ieee80211.h"
#include "third_party/iwlwifi/platform/banjo/softmac.h"
#include "third_party/iwlwifi/platform/wlansoftmac-device.h"
#include "third_party/iwlwifi/test/mock-function.h"
extern "C" {
#include "third_party/iwlwifi/iwl-nvm-parse.h"
#include "third_party/iwlwifi/mvm/mvm.h"
} // extern "C"
#include "third_party/iwlwifi/platform/mvm-mlme.h"
#include "third_party/iwlwifi/platform/scoped_utils.h"
#include "third_party/iwlwifi/platform/stats.h"
#include "third_party/iwlwifi/test/mock-trans.h"
#include "third_party/iwlwifi/test/single-ap-test.h"
#include "third_party/iwlwifi/test/wlan-pkt-builder.h"
namespace wlan::testing {
namespace {
constexpr size_t kListenInterval = 100;
constexpr uint8_t kInvalidBandIdFillByte = 0xa5;
constexpr wlan_band_t kInvalidBandId = 0xa5;
constexpr zx_handle_t kDummyMlmeChannel = 73939133; // An arbitrary value not ZX_HANDLE_INVALID
constexpr size_t kDefaultBeaconPeriod = 100;
constexpr size_t kNonDefaultBeaconPeriod = 123;
constexpr size_t kWlanSoftmacBandCapabilityBufferSize =
fidl::MaxSizeInChannel<fuchsia_wlan_softmac::wire::WlanSoftmacBandCapability,
fidl::MessageDirection::kReceiving>();
constexpr fuchsia_wlan_common::wire::BssType kDefaultBssType =
fuchsia_wlan_common::wire::BssType::kInfrastructure;
class WlanSoftmacDeviceTest : public SingleApTest,
public fdf::WireServer<fuchsia_wlan_softmac::WlanSoftmacIfc> {
public:
WlanSoftmacDeviceTest() : test_arena_(nullptr) {
mvmvif_ = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
mvmvif_->mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_->mlme_channel = kDummyMlmeChannel;
mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
mvmvif_->bss_conf = {.beacon_int = kListenInterval};
mvmvif_->mvm->mvmvif[0] = mvmvif_;
// mac_init() is called inside the constructor.
device_ =
std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(sim_trans_.iwl_trans(), 0, mvmvif_);
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmac>();
EXPECT_FALSE(endpoints.is_error());
device_->ServiceConnectHandler(sim_trans_.fdf_driver_dispatcher(),
std::move(endpoints->server));
client_ = fdf::WireSyncClient<fuchsia_wlan_softmac::WlanSoftmac>(std::move(endpoints->client));
// Create test arena.
auto arena = fdf::Arena::Create(0, 0);
EXPECT_FALSE(arena.is_error());
test_arena_ = *std::move(arena);
}
void Recv(RecvRequestView request, fdf::Arena& arena, RecvCompleter::Sync& completer) override {
recv_called_ = true;
completer.buffer(arena).Reply();
}
void ReportTxResult(ReportTxResultRequestView request, fdf::Arena& arena,
ReportTxResultCompleter::Sync& completer) override {
// Overriding the virtual function, not being used at this point.
completer.buffer(arena).Reply();
}
void NotifyScanComplete(NotifyScanCompleteRequestView request, fdf::Arena& arena,
NotifyScanCompleteCompleter::Sync& completer) override {
// Overriding the virtual function, not being used at this point.
completer.buffer(arena).Reply();
}
protected:
struct iwl_mvm_vif* mvmvif_;
std::unique_ptr<::wlan::iwlwifi::WlanSoftmacDevice> device_;
fdf::WireSyncClient<fuchsia_wlan_softmac::WlanSoftmac> client_;
fdf::UnownedSynchronizedDispatcher server_dispatcher_ =
sim_trans_.get_unowned_synchronized_dispatcher();
fdf::Arena test_arena_;
// The marks of WlanSoftmacIfc function calls.
bool recv_called_ = false;
};
//////////////////////////////////// Helper Functions /////////////////////////////////////////////
TEST_F(WlanSoftmacDeviceTest, ComposeBandList) {
struct iwl_nvm_data nvm_data;
fuchsia_wlan_common::WlanBand bands[fuchsia_wlan_common::wire::kMaxBands];
// nothing enabled
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
EXPECT_EQ(0, compose_band_list(&nvm_data, bands));
EXPECT_EQ(kInvalidBandId, uint8_t{bands[0]});
EXPECT_EQ(kInvalidBandId, uint8_t{bands[1]});
// 2.4GHz only
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
nvm_data.sku_cap_band_24ghz_enable = true;
EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kTwoGhz, bands[0]);
EXPECT_EQ(kInvalidBandId, uint8_t{bands[1]});
// 5GHz only
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
nvm_data.sku_cap_band_52ghz_enable = true;
EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kFiveGhz, bands[0]);
EXPECT_EQ(kInvalidBandId, uint8_t{bands[1]});
// both bands enabled
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
nvm_data.sku_cap_band_24ghz_enable = true;
nvm_data.sku_cap_band_52ghz_enable = true;
EXPECT_EQ(2, compose_band_list(&nvm_data, bands));
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kTwoGhz, bands[0]);
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kFiveGhz, bands[1]);
}
TEST_F(WlanSoftmacDeviceTest, FillBandCapabilityList) {
// The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
const struct iwl_nvm_data* nvm_data = iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data;
fuchsia_wlan_common::WlanBand bands[fuchsia_wlan_common::wire::kMaxBands];
size_t band_cap_count = compose_band_list(nvm_data, bands);
fidl::Arena<kWlanSoftmacBandCapabilityBufferSize> arena;
ASSERT_LE(band_cap_count, fuchsia_wlan_common::wire::kMaxBands);
fuchsia_wlan_softmac::wire::WlanSoftmacBandCapability
band_cap_list[fuchsia_wlan_common_MAX_BANDS] = {};
fill_band_cap_list(nvm_data, bands, band_cap_count, arena, band_cap_list);
// 2.4Ghz
fuchsia_wlan_softmac::wire::WlanSoftmacBandCapability* band_cap = &band_cap_list[0];
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kTwoGhz, band_cap->band());
EXPECT_TRUE(band_cap->has_ht_caps());
EXPECT_EQ(12, band_cap->basic_rate_count());
EXPECT_EQ(2, band_cap->basic_rate_list()[0]); // 1Mbps
EXPECT_EQ(108, band_cap->basic_rate_list()[11]); // 54Mbps
EXPECT_EQ(13, band_cap->operating_channel_count());
EXPECT_EQ(1, band_cap->operating_channel_list()[0]);
EXPECT_EQ(13, band_cap->operating_channel_list()[12]);
// 5GHz
band_cap = &band_cap_list[1];
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kFiveGhz, band_cap->band());
EXPECT_TRUE(band_cap->has_ht_caps());
EXPECT_EQ(8, band_cap->basic_rate_count());
EXPECT_EQ(12, band_cap->basic_rate_list()[0]); // 6Mbps
EXPECT_EQ(108, band_cap->basic_rate_list()[7]); // 54Mbps
EXPECT_EQ(25, band_cap->operating_channel_count());
EXPECT_EQ(36, band_cap->operating_channel_list()[0]);
EXPECT_EQ(165, band_cap->operating_channel_list()[24]);
}
TEST_F(WlanSoftmacDeviceTest, FillBandCapabilityListOnly5GHz) {
// The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
fidl::Arena<kWlanSoftmacBandCapabilityBufferSize> arena;
fuchsia_wlan_common::WlanBand bands[fuchsia_wlan_common::wire::kMaxBands] = {
fuchsia_wlan_common::WlanBand::kFiveGhz,
};
fuchsia_wlan_softmac::wire::WlanSoftmacBandCapability
band_cap_list[fuchsia_wlan_common::wire::kMaxBands] = {};
fill_band_cap_list(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, 1, arena,
band_cap_list);
// 5GHz
fuchsia_wlan_softmac::wire::WlanSoftmacBandCapability* band_cap = &band_cap_list[0];
EXPECT_EQ(fuchsia_wlan_common::WlanBand::kFiveGhz, band_cap->band());
EXPECT_TRUE(band_cap->has_ht_caps());
EXPECT_EQ(8, band_cap->basic_rate_count());
EXPECT_EQ(12, band_cap->basic_rate_list()[0]); // 6Mbps
EXPECT_EQ(108, band_cap->basic_rate_list()[7]); // 54Mbps
EXPECT_EQ(25, band_cap->operating_channel_count());
EXPECT_EQ(36, band_cap->operating_channel_list()[0]);
EXPECT_EQ(165, band_cap->operating_channel_list()[24]);
// index 1 should be empty.
band_cap = &band_cap_list[1];
EXPECT_FALSE(band_cap->has_ht_caps());
EXPECT_FALSE(band_cap->has_basic_rate_list());
EXPECT_FALSE(band_cap->has_operating_channel_list());
}
TEST_F(WlanSoftmacDeviceTest, Query) {
auto result = client_.buffer(test_arena_)->Query();
auto& info = *result->value();
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
EXPECT_TRUE(info.has_mac_role());
EXPECT_EQ(fuchsia_wlan_common::wire::WlanMacRole::kClient, info.mac_role());
//
// The below code assumes the test/sim-default-nvm.cc contains 2 bands.
//
// .band_cap_list[0]: fuchsia_wlan_common::WlanBand::kTwoGhz
// .band_cap_list[1]: fuchsia_wlan_common::WlanBand::kFiveGhz
//
ASSERT_EQ(2, info.band_caps().count());
EXPECT_EQ(12, info.band_caps().data()[0].basic_rate_count());
EXPECT_EQ(2, info.band_caps().data()[0].basic_rate_list()[0]); // 1 Mbps
EXPECT_EQ(36, info.band_caps().data()[0].basic_rate_list()[7]); // 18 Mbps
EXPECT_EQ(108, info.band_caps().data()[0].basic_rate_list()[11]); // 54 Mbps
EXPECT_EQ(8, info.band_caps().data()[1].basic_rate_count());
EXPECT_EQ(12, info.band_caps().data()[1].basic_rate_list()[0]); // 6 Mbps
EXPECT_EQ(165, info.band_caps().data()[1].operating_channel_list()[24]);
}
TEST_F(WlanSoftmacDeviceTest, DiscoveryFeatureQuery) {
auto result = client_.buffer(test_arena_)->QueryDiscoverySupport();
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
EXPECT_TRUE(result->value()->resp.scan_offload.supported);
EXPECT_FALSE(result->value()->resp.probe_response_offload.supported);
}
TEST_F(WlanSoftmacDeviceTest, MacSublayerFeatureQuery) {
auto result = client_.buffer(test_arena_)->QueryMacSublayerSupport();
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
EXPECT_FALSE(result->value()->resp.rate_selection_offload.supported);
EXPECT_EQ(result->value()->resp.device.mac_implementation_type,
fuchsia_wlan_common::wire::MacImplementationType::kSoftmac);
EXPECT_FALSE(result->value()->resp.device.is_synthetic);
EXPECT_EQ(result->value()->resp.data_plane.data_plane_type,
fuchsia_wlan_common::wire::DataPlaneType::kEthernetDevice);
}
TEST_F(WlanSoftmacDeviceTest, SecurityFeatureQuery) {
auto result = client_.buffer(test_arena_)->QuerySecuritySupport();
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
EXPECT_TRUE(result->value()->resp.mfp.supported);
EXPECT_FALSE(result->value()->resp.sae.driver_handler_supported);
EXPECT_TRUE(result->value()->resp.sae.sme_handler_supported);
}
TEST_F(WlanSoftmacDeviceTest, SpectrumManagementFeatureQuery) {
auto result = client_.buffer(test_arena_)->QuerySpectrumManagementSupport();
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
EXPECT_TRUE(result->value()->resp.dfs.supported);
}
TEST_F(WlanSoftmacDeviceTest, MacStart) {
// Created the end points for WlanSoftmacIfc protocol, and pass the client end to
// WlanSoftmacDevice.
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacIfc>();
ASSERT_FALSE(endpoints.is_error());
fdf::BindServer(server_dispatcher_->get(), std::move(endpoints->server), this);
// This FIDL call should invoke mac_start() and pass the pointer of WlanSoftmacDeviceTest to it,
// the pointer will be copied to mvmvif_->ifc.ctx.
auto result = client_.buffer(test_arena_)->Start(std::move(endpoints->client));
EXPECT_TRUE(result.ok());
EXPECT_FALSE(result->is_error());
}
TEST_F(WlanSoftmacDeviceTest, MacStartOnlyOneMlmeChannelAllowed) {
// The normal case. A channel will be transferred to MLME.
constexpr zx_handle_t kMlmeChannel = static_cast<zx_handle_t>(0xF001);
// Manually set mlme channel here, in reality, this is set when WlanPhyImplDevice::CreateIface()
// is called.
mvmvif_->mlme_channel = kMlmeChannel;
{
// Don't need to bind the server here because we won't need to use WlanSoftmacIfc calls.
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacIfc>();
ASSERT_FALSE(endpoints.is_error());
auto result = client_.buffer(test_arena_)->Start(std::move(endpoints->client));
EXPECT_TRUE(result.ok());
EXPECT_FALSE(result->is_error());
// Verify the channel returned.
ASSERT_EQ(result->value()->sme_channel, kMlmeChannel);
ASSERT_EQ(mvmvif_->mlme_channel, ZX_HANDLE_INVALID); // Driver no longer holds the ownership.
}
{
// Create another pair of endpoints for the next call.
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacIfc>();
// Since the driver no longer owns the handle, the start should fail.
auto result = client_.buffer(test_arena_)->Start(std::move(endpoints->client));
EXPECT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
EXPECT_EQ(ZX_ERR_ALREADY_BOUND, result->error_value());
}
}
TEST_F(WlanSoftmacDeviceTest, SingleRxPacket) {
// Created the end points for WlanSoftmacIfc protocol, and pass the client end to
// WlanSoftmacDevice.
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacIfc>();
ASSERT_FALSE(endpoints.is_error());
fdf::BindServer(server_dispatcher_->get(), std::move(endpoints->server), this);
// This FIDL call should invoke mac_start() and pass the pointer of WlanSoftmacDeviceTest to it,
// the pointer will be copied to mvmvif_->ifc.ctx.
auto result = client_.buffer(test_arena_)->Start(std::move(endpoints->client));
EXPECT_TRUE(result.ok());
EXPECT_FALSE(result->is_error());
// Create an dummy rx packet.
// TODO(fxbug.dev/99777): Integrate it into WlanPktBuilder.
const uint8_t kMacPkt[] = {
0x08, 0x01, // frame_ctrl
0x00, 0x00, // duration
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // MAC1
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // MAC2
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // MAC3
0x00, 0x00, // seq_ctrl
0x45, 0x00, 0x55, 0x66, 0x01, 0x83, // random IP packet...
};
wlan_rx_packet_t rx_packet = {
.mac_frame_buffer = &kMacPkt[0],
.mac_frame_size = sizeof(kMacPkt),
.info =
{
.rx_flags = 0,
.phy = WLAN_PHY_TYPE_DSSS,
},
};
// The above lines should enable WlanSoftmacIfc FIDL calls, and the lines below is verifing that.
mvmvif_->ifc.recv(mvmvif_->ifc.ctx, &rx_packet);
EXPECT_TRUE(recv_called_);
}
TEST_F(WlanSoftmacDeviceTest, Release) {
// Create a channel. Let this test case holds one end while driver holds the other end.
char dummy[1];
zx_handle_t case_end;
ASSERT_EQ(zx_channel_create(0 /* option */, &case_end, &mvmvif_->mlme_channel), ZX_OK);
ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0), ZX_OK);
// Destroy the device and the sme channel should be closed so that we will get a peer-close error
// while trying to write any data to it.
device_.reset();
ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0),
ZX_ERR_PEER_CLOSED);
}
// The class for WLAN device MAC testing.
//
class MacInterfaceTest : public WlanSoftmacDeviceTest, public MockTrans {
public:
MacInterfaceTest() {
zx_handle_t wlan_phy_impl_channel = mvmvif_->mlme_channel;
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacIfc>();
EXPECT_FALSE(endpoints.is_error());
// Created the end points for WlanSoftmacIfc protocol, and pass the client end to
// WlanSoftmacDevice.
fdf::BindServer(server_dispatcher_->get(), std::move(endpoints->server), this);
// This FIDL call should invoke mac_start() and pass the pointer of WlanSoftmacDeviceTest to it,
// the pointer will be copied to mvmvif_->ifc.ctx.
auto result = client_.buffer(test_arena_)->Start(std::move(endpoints->client));
EXPECT_TRUE(result.ok());
EXPECT_FALSE(result->is_error());
EXPECT_EQ(wlan_phy_impl_channel, result->value()->sme_channel);
// Add the interface to MVM instance.
mvmvif_->mvm->mvmvif[0] = mvmvif_;
original_send_cmd = nullptr;
}
~MacInterfaceTest() {
VerifyExpectation(); // Ensure all expectations had been met.
// Restore the original callback for other test cases not using the mock.
ResetSendCmdFunc();
// Stop the MAC to free resources we allocated.
// This must be called after we verify the expected commands and restore the mock command
// callback so that the stop command doesn't mess up the test case expectation.
auto result = client_.buffer(test_arena_)->Stop();
EXPECT_TRUE(result.ok());
// Deallocate the client station at id 0.
mtx_lock(&mvmvif_->mvm->mutex);
iwl_mvm_del_aux_sta(mvmvif_->mvm);
mtx_unlock(&mvmvif_->mvm->mutex);
VerifyStaHasBeenRemoved();
}
// Used in MockCommand constructor to indicate if the command needs to be either
//
// - returned immediately (with a status code), or
// - passed to the sim_mvm.c.
//
enum SimMvmBehavior {
kSimMvmReturnWithStatus,
kSimMvmBypassToSimMvm,
};
// A flexible mock-up of firmware command for testing code. Testing code can decide to either call
// the simulated firmware or return the status code immediately.
//
// cmd_id: the command ID. Sometimes composed with WIDE_ID() macro.
// behavior: determine what this mockup command is to do.
// status: the status code to return when behavior is 'kSimMvmReturnWithStatus'.
//
class MockCommand {
public:
MockCommand(uint32_t cmd_id, SimMvmBehavior behavior, zx_status_t status)
: cmd_id_(cmd_id), behavior_(behavior), status_(status) {}
MockCommand(uint32_t cmd_id) : MockCommand(cmd_id, kSimMvmBypassToSimMvm, ZX_OK) {}
~MockCommand() {}
uint32_t cmd_id_;
SimMvmBehavior behavior_;
zx_status_t status_;
};
typedef std::list<MockCommand> expected_cmd_id_list;
typedef zx_status_t (*fp_send_cmd)(struct iwl_trans* trans, struct iwl_host_cmd* cmd);
// Public for MockSendCmd().
expected_cmd_id_list expected_cmd_ids;
fp_send_cmd original_send_cmd;
protected:
bool IsValidChannel(const fuchsia_wlan_common::wire::WlanChannel* channel) {
return device_->IsValidChannel(channel);
}
zx_status_t SetChannel(const fuchsia_wlan_common::wire::WlanChannel* channel) {
fidl::Arena fidl_arena;
auto builder = fuchsia_wlan_softmac::wire::WlanSoftmacSetChannelRequest::Builder(fidl_arena);
builder.channel(*channel);
auto result = client_.buffer(test_arena_)->SetChannel(builder.Build());
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
zx_status_t JoinBssRequest(uint16_t beacon_period = kDefaultBeaconPeriod,
fuchsia_wlan_common::wire::BssType bss_type = kDefaultBssType) {
fidl::Arena fidl_arena;
auto builder = fuchsia_wlan_common::wire::JoinBssRequest::Builder(fidl_arena);
builder.bssid(kBssid);
builder.bss_type(bss_type);
builder.remote(true);
builder.beacon_period(beacon_period);
auto result = client_.buffer(test_arena_)->JoinBss(builder.Build());
EXPECT_TRUE(result.ok());
if (result->is_error()) {
EXPECT_NE(ZX_OK, result->error_value());
return result->error_value();
}
return ZX_OK;
}
zx_status_t NotifyAssociationComplete() {
fidl::Arena fidl_arena;
auto builder = fuchsia_wlan_softmac::wire::WlanAssociationConfig::Builder(fidl_arena);
builder.listen_interval(kListenInterval);
builder.channel(fuchsia_wlan_common::wire::WlanChannel{
.primary = 157,
.cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw80,
});
builder.rates(fidl::VectorView(fidl_arena, std::vector<uint8_t>({140})));
auto result = client_.buffer(test_arena_)->NotifyAssociationComplete(builder.Build());
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
zx_status_t ConfigureHtAssoc() {
fidl::Arena fidl_arena;
auto builder = fuchsia_wlan_softmac::wire::WlanAssociationConfig::Builder(fidl_arena);
builder.listen_interval(kListenInterval);
builder.channel(fuchsia_wlan_common::wire::WlanChannel{
.primary = 157,
.cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw80,
});
builder.rates(fidl::VectorView(fidl_arena, std::vector<uint8_t>({
140,
18,
152,
36,
176,
72,
96,
108,
})));
builder.ht_cap(fuchsia_wlan_ieee80211::wire::HtCapabilities{
.bytes =
{
.data_ =
{
0,
0, // HtCapabilityInfo
0, // AmpduParams
255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, // Supported mcs set
0,
0, // HtExtCapabilities
0, 0, 0,
0, // TxBeamformingCapabilities
0, // AselCapability
},
},
});
auto result = client_.buffer(test_arena_)->NotifyAssociationComplete(builder.Build());
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
zx_status_t ClearAssociation() {
// Not used since all info were saved in mvmvif_sta_ already.
fidl::Arena fidl_arena;
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacClearAssociationRequest::Builder(fidl_arena);
fidl::Array<uint8_t, fuchsia_wlan_ieee80211::wire::kMacAddrLen> fidl_peer_addr;
builder.peer_addr(fidl_peer_addr);
auto result = client_.buffer(test_arena_)->ClearAssociation(builder.Build());
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
zx_status_t StartPassiveScan(
fuchsia_wlan_softmac::wire::WlanSoftmacStartPassiveScanRequest* args) {
auto result = client_.buffer(test_arena_)->StartPassiveScan(*args);
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
zx_status_t StartActiveScan(fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanRequest* args) {
auto result = client_.buffer(test_arena_)->StartActiveScan(*args);
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
zx_status_t InstallKey(const fuchsia_wlan_softmac::wire::WlanKeyConfiguration* key_config) {
IWL_INFO(nullptr, "Calling set_key");
auto result = client_.buffer(test_arena_)->InstallKey(*key_config);
EXPECT_TRUE(result.ok());
if (result->is_error()) {
return result->error_value();
}
return ZX_OK;
}
// The following functions are for mocking up the firmware commands.
//
// The mock function will return the special error ZX_ERR_INTERNAL when the expectation
// is not expected.
// Set the expected commands sending to the firmware.
//
// Args:
// cmd_ids: list of expected commands. Will be matched in order.
//
void ExpectSendCmd(const expected_cmd_id_list& cmd_ids) {
expected_cmd_ids = cmd_ids;
// Re-define the 'dev' field in the 'struct iwl_trans' to a test instance of this class.
sim_trans_.iwl_trans()->dev = reinterpret_cast<struct device*>(this);
// Setup the mock function for send command.
original_send_cmd = sim_trans_.iwl_trans()->ops->send_cmd;
sim_trans_.iwl_trans()->ops->send_cmd = MockSendCmd;
}
// Reset the send command function to the original one, so that the test case would stop checking
// commands one by one.
void ResetSendCmdFunc() {
if (original_send_cmd) {
IWL_INFO(nullptr, "Reseting send_cmd.");
sim_trans_.iwl_trans()->ops->send_cmd = original_send_cmd;
} else {
IWL_WARN(nullptr, "No original send_cmd found.");
}
}
static zx_status_t MockSendCmd(struct iwl_trans* trans, struct iwl_host_cmd* cmd) {
MacInterfaceTest* this_ = reinterpret_cast<MacInterfaceTest*>(trans->dev);
// remove the first one and match.
expected_cmd_id_list& expected = this_->expected_cmd_ids;
ZX_ASSERT_MSG(!expected.empty(),
"A command (0x%04x) is going to send, but no command is expected.\n", cmd->id);
// check the command ID.
auto exp = expected.front();
ZX_ASSERT_MSG(exp.cmd_id_ == cmd->id,
"The command doesn't match! Expect: 0x%04x, actual: 0x%04x.\n", exp.cmd_id_,
cmd->id);
expected.pop_front();
if (exp.behavior_ == kSimMvmBypassToSimMvm) {
return this_->original_send_cmd(trans, cmd);
} else {
return exp.status_;
}
}
void VerifyExpectation() {
for (expected_cmd_id_list::iterator it = expected_cmd_ids.begin(); it != expected_cmd_ids.end();
it++) {
printf(" ==> 0x%04x\n", it->cmd_id_);
}
EXPECT_TRUE(expected_cmd_ids.empty());
mock_tx_.VerifyAndClear();
}
void VerifyStaHasBeenRemoved() {
auto mvm = mvmvif_->mvm;
for (size_t i = 0; i < std::size(mvm->fw_id_to_mac_id); i++) {
struct iwl_mvm_sta* mvm_sta = mvm->fw_id_to_mac_id[i];
ASSERT_EQ(nullptr, mvm_sta);
}
ASSERT_EQ(0, mvm->vif_count);
}
// Mock function for Tx.
mock_function::MockFunction<zx_status_t, // return value
size_t, // packet size
uint16_t, // cmd + group_id
int // txq_id
>
mock_tx_;
static zx_status_t tx_wrapper(struct iwl_trans* trans, struct ieee80211_mac_packet* pkt,
struct iwl_device_tx_cmd* dev_cmd, int txq_id) {
iwl_stats_inc(IWL_STATS_CNT_DATA_TO_FW); // to simulate the iwl_trans_pcie_tx() behavior.
auto test = GET_TEST(MacInterfaceTest, trans);
return test->mock_tx_.Call(pkt->header_size + pkt->headroom_used_size + pkt->body_size,
WIDE_ID(dev_cmd->hdr.group_id, dev_cmd->hdr.cmd), txq_id);
}
static constexpr fidl::Array<uint8_t, 6> kBssid = {
.data_ = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
};
static constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
static constexpr fidl::Array<uint8_t, 32> kFakeKey = {
.data_ = {1, 2, 3, 4, 5, 6, 7, 8, 0, 9, 8, 7, 6, 5, 4, 3},
};
static constexpr size_t kFakeKeyLen = 16;
static constexpr fidl::Array<uint8_t, 32> kFakeTkipKey = {
.data_ = {1, 2, 3, 4, 5, 6, 7, 8, 0, 9, 8, 7, 6, 5, 4, 3,
1, 2, 3, 4, 5, 6, 7, 8, 0, 9, 8, 7, 6, 5, 4, 3},
};
static constexpr size_t kFakeTkipKeyLen = 32;
// Define it's own kChannel and override the one defined in SingleApTest.
static constexpr fuchsia_wlan_common::wire::WlanChannel kChannel = {
.primary = 11, .cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw20};
static constexpr fuchsia_wlan_common::wire::WlanChannel kChannel2 = {
.primary = 161, .cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw80};
static constexpr uint16_t kChannelSize = 4;
};
TEST_F(MacInterfaceTest, TestIsValidChannel) {
ExpectSendCmd(expected_cmd_id_list({}));
fuchsia_wlan_common::wire::WlanChannel ch10_20m = {
.primary = 10,
.cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw20,
};
EXPECT_TRUE(IsValidChannel(&ch10_20m));
fuchsia_wlan_common::wire::WlanChannel ch10_40m = {
.primary = 10,
.cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw40,
};
EXPECT_FALSE(IsValidChannel(&ch10_40m));
fuchsia_wlan_common::wire::WlanChannel ch13_40m_below = {
.primary = 13,
.cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw40Below,
};
EXPECT_FALSE(IsValidChannel(&ch13_40m_below));
fuchsia_wlan_common::wire::WlanChannel ch5_80m = {
.primary = 5,
.cbw = fuchsia_wlan_common::ChannelBandwidth::kCbw80,
};
EXPECT_FALSE(IsValidChannel(&ch5_80m));
}
// Test the set_channel().
//
TEST_F(MacInterfaceTest, TestSetChannel) {
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
}));
mvmvif_->csa_bcn_pending = true; // Expect to be clear because this is client role.
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
EXPECT_FALSE(mvmvif_->csa_bcn_pending);
}
// Test call set_channel() multiple times.
//
TEST_F(MacInterfaceTest, TestMultipleSetChannel) {
ExpectSendCmd(expected_cmd_id_list({
// for the first SetChannel()
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
// for the second SetChannel()
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)), // for remove_chanctx
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
}));
for (size_t i = 0; i < 2; i++) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
}
}
// Test the unsupported MAC role.
//
TEST_F(MacInterfaceTest, TestSetChannelWithUnsupportedRole) {
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
}));
mvmvif_->mac_role = WLAN_MAC_ROLE_AP;
ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, SetChannel(&kChannel));
}
// Call SetChannel() twice to simulate a channel switch announcement.
TEST_F(MacInterfaceTest, DuplicateSetChannel) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
struct iwl_mvm_phy_ctxt* phy_ctxt = mvmvif_->phy_ctxt;
ASSERT_NE(nullptr, phy_ctxt);
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
// Call SetChannel() again. This should return the same phy context.
// The BSS info should be the same (because we only change the channel).
ASSERT_EQ(ZX_OK, SetChannel(&kChannel2));
struct iwl_mvm_phy_ctxt* new_phy_ctxt = mvmvif_->phy_ctxt;
ASSERT_NE(nullptr, new_phy_ctxt);
ASSERT_EQ(phy_ctxt, new_phy_ctxt);
struct iwl_mvm_sta* new_mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(new_mvm_sta, mvm_sta);
}
// Test JoinBss()
//
TEST_F(MacInterfaceTest, TestJoinBss) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(mvmvif_->bss_conf.beacon_int, kDefaultBeaconPeriod);
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, SCD_QUEUE_CFG)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
}));
// Send with a different beacon period so that we see change in mvmvif_->bss_conf
ASSERT_EQ(ZX_OK, JoinBssRequest(kNonDefaultBeaconPeriod));
// Ensure the BSSID was copied into mvmvif
ASSERT_EQ(memcmp(mvmvif_->bss_conf.bssid, kBssid.data(), ETH_ALEN), 0);
ASSERT_EQ(memcmp(mvmvif_->bssid, kBssid.data(), ETH_ALEN), 0);
ASSERT_EQ(mvmvif_->bss_conf.beacon_int, kNonDefaultBeaconPeriod);
}
// Test duplicate BSS config.
//
TEST_F(MacInterfaceTest, DuplicateJoinBss) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
// The second configuration will unconfigure the previous one first.
ExpectSendCmd(expected_cmd_id_list({
// called by mac_leave_bss().
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, TXPATH_FLUSH)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, TXPATH_FLUSH)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, SCD_QUEUE_CFG)),
MockCommand(WIDE_ID(LONG_GROUP, REMOVE_STA)),
// mac_join_bss()
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, SCD_QUEUE_CFG)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
}));
ASSERT_EQ(ZX_OK, JoinBssRequest());
}
// Test unsupported bss_type.
//
TEST_F(MacInterfaceTest, UnsupportedBssType) {
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
JoinBssRequest(kDefaultBeaconPeriod, fuchsia_wlan_common::wire::BssType::kIndependent));
}
TEST_F(MacInterfaceTest, UnsupportedBeaconPeriod) {
ASSERT_EQ(ZX_ERR_INVALID_ARGS, JoinBssRequest(IWL_MIN_BEACON_PERIOD_TU - 1,
fuchsia_wlan_common::wire::BssType::kIndependent));
}
// Test failed ADD_STA command.
//
TEST_F(MacInterfaceTest, TestFailedAddSta) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD), kSimMvmReturnWithStatus,
ZX_ERR_BUFFER_TOO_SMALL /* an arbitrary error */),
}));
ASSERT_EQ(ZX_ERR_BUFFER_TOO_SMALL, JoinBssRequest());
}
// Test whether the AUX sta (for active scan) is added.
//
TEST_F(MacInterfaceTest, TestAuxSta) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
auto aux_sta = &mvmvif_->mvm->aux_sta;
EXPECT_EQ(IWL_STA_AUX_ACTIVITY, aux_sta->type);
EXPECT_NE(IWL_MVM_INVALID_STA, aux_sta->sta_id);
EXPECT_NE(nullptr, mvmvif_->mvm->fw_id_to_mac_id[0]); // Assume it is the first one.
// The removal check is done in VerifyStaHasBeenRemoved() of MacInterfaceTest deconstructor.
}
// Test exception handling in driver.
//
TEST_F(MacInterfaceTest, TestExceptionHandling) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
// Test the phy_ctxt checking.
auto backup_phy_ctxt = mvmvif_->phy_ctxt;
mvmvif_->phy_ctxt = nullptr;
EXPECT_EQ(ZX_ERR_BAD_STATE, JoinBssRequest());
mvmvif_->phy_ctxt = backup_phy_ctxt;
iwl_mvm_sta sta;
// Test the case we run out of slots for STA.
// Occupy all the station slots.
for (uint16_t i = 0; i < IWL_MVM_STATION_COUNT; i++) {
mvmvif_->mvm->fw_id_to_mac_id[i] = &sta;
}
// Request fails because we run out of all slots in fw_id_to_mac_id[].
EXPECT_EQ(ZX_ERR_NO_RESOURCES, JoinBssRequest());
// Clean up all the station slots.
for (uint16_t i = 0; i < IWL_MVM_STATION_COUNT; i++) {
mvmvif_->mvm->fw_id_to_mac_id[i] = nullptr;
}
EXPECT_EQ(ZX_OK, JoinBssRequest());
// iwl_mvm_add_sta(), called by JoinBss(), has an assumption that each interface has only one
// AP sta (for WLAN_MAC_ROLE_CLIENT). However, in this case, we break the assumption so that the
// ap_sta_id was populated with the last successful STA ID. Thus, we reset the mvmvif_->ap_sta_id
// so that the SoftMacDevices destructor will not release the resource twice by calling
// ClearAssociation().
mvmvif_->ap_sta_id = IWL_MVM_INVALID_STA;
}
// The test is used to test the typical procedure to connect to an open network.
//
TEST_F(MacInterfaceTest, AssociateToOpenNetwork) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, NotifyAssociationComplete());
ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
ASSERT_TRUE(mvmvif_->bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
ASSERT_EQ(mvm_sta->sta_state, iwl_sta_state::IWL_STA_AUTHORIZED);
ASSERT_EQ(ZX_OK, ClearAssociation());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
}
// Check if calling iwl_mvm_mac_sta_state() sets the state correctly.
TEST_F(MacInterfaceTest, CheckStaState) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, NotifyAssociationComplete());
ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
ASSERT_TRUE(mvmvif_->bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
ASSERT_EQ(mvm_sta->sta_state, iwl_sta_state::IWL_STA_AUTHORIZED);
ASSERT_EQ(ZX_OK, iwl_mvm_mac_sta_state(mvmvif_, mvm_sta, IWL_STA_AUTHORIZED, IWL_STA_ASSOC));
ASSERT_EQ(mvm_sta->sta_state, iwl_sta_state::IWL_STA_ASSOC);
ASSERT_EQ(ZX_OK, ClearAssociation());
}
// Back to back calls of ClearAssociation().
TEST_F(MacInterfaceTest, ClearAssociationAfterClearAssociation) {
ASSERT_NE(ZX_OK, ClearAssociation());
ASSERT_NE(ZX_OK, ClearAssociation());
}
// ClearAssociation() should cleanup when called without Assoc
TEST_F(MacInterfaceTest, ClearAssociationAfterNoAssoc) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ClearAssociation());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
// Call ClearAssociation() again to check if it is handled correctly.
ASSERT_NE(ZX_OK, ClearAssociation());
}
// ClearAssociation() should cleanup when called after a failed Assoc
TEST_F(MacInterfaceTest, ClearAssociationAfterFailedAssoc) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
// Fail the association by forcing some relevant internal state.
auto orig = mvmvif_->uploaded;
mvmvif_->uploaded = false;
ASSERT_EQ(ZX_ERR_IO, NotifyAssociationComplete());
mvmvif_->uploaded = orig;
// ClearAssociation will clean up the failed association.
ASSERT_EQ(ZX_OK, ClearAssociation());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
// Call ClearAssociation() again to check if it is handled correctly.
ASSERT_NE(ZX_OK, ClearAssociation());
}
// This test case is to verify NotifyAssociationComplete() with HT wlan_association_config_t input
// can successfully trigger LQ_CMD with correct data.
TEST_F(MacInterfaceTest, AssocWithHtConfig) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, LQ_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MCAST_FILTER_CMD)),
}));
// Extract LQ_CMD data.
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
struct iwl_lq_cmd* lq_cmd = &mvm_sta->lq_sta.rs_drv.lq;
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ConfigureHtAssoc());
// Verify the values in LQ_CMD API structure.
EXPECT_EQ(lq_cmd->sta_id, 0);
EXPECT_EQ(lq_cmd->reduced_tpc, 0);
EXPECT_EQ(lq_cmd->flags, 0);
EXPECT_EQ(lq_cmd->mimo_delim, 0);
EXPECT_EQ(lq_cmd->single_stream_ant_msk, 1);
EXPECT_EQ(lq_cmd->dual_stream_ant_msk, 3);
EXPECT_EQ(lq_cmd->initial_rate_index[0], 0);
EXPECT_EQ(lq_cmd->initial_rate_index[1], 0);
EXPECT_EQ(lq_cmd->initial_rate_index[2], 0);
EXPECT_EQ(lq_cmd->initial_rate_index[3], 0);
EXPECT_EQ(lq_cmd->agg_time_limit, 0x0fa0);
EXPECT_EQ(lq_cmd->agg_disable_start_th, 3);
EXPECT_EQ(lq_cmd->agg_frame_cnt_limit, 1);
EXPECT_EQ(lq_cmd->reserved2, 0);
// Verify rate_n_flags in the table.
EXPECT_EQ(lq_cmd->rs_table[0], 0x4103);
// The value of RS_MNG_RETRY_TABLE_INITIAL_RATE_NUM is 3.
EXPECT_EQ(lq_cmd->rs_table[3], 0x4102);
EXPECT_EQ(lq_cmd->ss_params, 0);
// Stop checking following commands one by one.
ResetSendCmdFunc();
// Clean up the association states.
ASSERT_EQ(ZX_OK, ClearAssociation());
}
TEST_F(MacInterfaceTest, StartPassiveScanTest) {
fidl::Arena fidl_arena;
// FromExternal() is not able to take const data.
uint8_t channels_to_scan[kChannelSize] = {7, 1, 40, 136};
{
// Passive scan with some random channels should pass.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartPassiveScanRequest::Builder(fidl_arena);
builder.channels(fidl::VectorView<uint8_t>::FromExternal(&channels_to_scan[0], kChannelSize));
auto passive_scan_args = builder.Build();
ASSERT_EQ(ZX_OK, StartPassiveScan(&passive_scan_args));
}
{
// Passive scan request will fail in mvm-mlme.cc if the channels field is not set.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartPassiveScanRequest::Builder(fidl_arena);
auto passive_scan_args = builder.Build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartPassiveScan(&passive_scan_args));
}
}
TEST_F(MacInterfaceTest, StartActiveScanTest) {
fidl::Arena fidl_arena;
// FromExternal() is not able to take const data.
uint8_t channels_to_scan[kChannelSize] = {7, 1, 40, 136};
{
// Active scan with args in which all of "channels", "ssids", "mac_header" and "ies" fields are
// set will pass the argument check in mvm-mlme.cc.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanRequest::Builder(fidl_arena);
builder.channels(fidl::VectorView<uint8_t>::FromExternal(&channels_to_scan[0], kChannelSize));
builder.ssids(fidl::VectorView<fuchsia_wlan_ieee80211::wire::CSsid>());
builder.mac_header(fidl::VectorView<uint8_t>());
builder.ies(fidl::VectorView<uint8_t>());
auto active_scan_args = builder.Build();
ASSERT_EQ(ZX_OK, StartActiveScan(&active_scan_args));
}
{
// Missing "channels" fails the argument check.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanRequest::Builder(fidl_arena);
builder.ssids(fidl::VectorView<fuchsia_wlan_ieee80211::wire::CSsid>());
builder.mac_header(fidl::VectorView<uint8_t>());
builder.ies(fidl::VectorView<uint8_t>());
auto active_scan_args = builder.Build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartActiveScan(&active_scan_args));
}
{
// Missing "ssids" fails the argument check.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanRequest::Builder(fidl_arena);
builder.channels(fidl::VectorView<uint8_t>::FromExternal(&channels_to_scan[0], kChannelSize));
builder.mac_header(fidl::VectorView<uint8_t>());
builder.ies(fidl::VectorView<uint8_t>());
auto active_scan_args = builder.Build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartActiveScan(&active_scan_args));
}
{
// Missing "mac_header" fails the argument check.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanRequest::Builder(fidl_arena);
builder.channels(fidl::VectorView<uint8_t>::FromExternal(&channels_to_scan[0], kChannelSize));
builder.ssids(fidl::VectorView<fuchsia_wlan_ieee80211::wire::CSsid>());
builder.ies(fidl::VectorView<uint8_t>());
auto active_scan_args = builder.Build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartActiveScan(&active_scan_args));
}
{
// Missing "ies" fails the argument check.
auto builder =
fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanRequest::Builder(fidl_arena);
builder.channels(fidl::VectorView<uint8_t>::FromExternal(&channels_to_scan[0], kChannelSize));
builder.ssids(fidl::VectorView<fuchsia_wlan_ieee80211::wire::CSsid>());
builder.mac_header(fidl::VectorView<uint8_t>());
auto active_scan_args = builder.Build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, StartActiveScan(&active_scan_args));
}
}
// Check to ensure keys are set during assoc and deleted after disassoc
// for now use open network
TEST_F(MacInterfaceTest, InstallKeysTest) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, NotifyAssociationComplete());
ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
ASSERT_TRUE(mvmvif_->bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
fidl::Arena fidl_arena;
fidl::Array<uint8_t, 3> cipher_oui;
auto key =
fidl::VectorView<uint8_t>::FromExternal(const_cast<uint8_t*>(kFakeKey.begin()), kFakeKeyLen);
memcpy(cipher_oui.begin(), kIeeeOui, 3);
{
auto builder = fuchsia_wlan_softmac::wire::WlanKeyConfiguration::Builder(fidl_arena);
// Set an arbitrary pairwise key.
builder.cipher_type(4);
builder.key_type(fuchsia_wlan_common::wire::WlanKeyType::kPairwise);
builder.key_idx(0);
builder.key(key);
builder.rsc(0);
builder.cipher_oui(cipher_oui);
auto key_config = builder.Build();
ASSERT_EQ(ZX_OK, InstallKey(&key_config));
// Expect bit 0 to be set.
ASSERT_EQ(*mvm->fw_key_table, 0x1);
}
{
auto builder = fuchsia_wlan_softmac::wire::WlanKeyConfiguration::Builder(fidl_arena);
// Set an arbitrary group key.
builder.cipher_type(4);
builder.key_type(fuchsia_wlan_common::wire::WlanKeyType::kGroup);
builder.key_idx(1);
builder.key(key);
builder.rsc(0);
builder.cipher_oui(cipher_oui);
auto key_config = builder.Build();
ASSERT_EQ(ZX_OK, InstallKey(&key_config));
// Expect bit 1 to be set as well.
ASSERT_EQ(*mvm->fw_key_table, 0x3);
}
ASSERT_EQ(ZX_OK, ClearAssociation());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
// Both the keys should have been deleted.
ASSERT_EQ(*mvm->fw_key_table, 0x0);
}
// Check that we can sucessfully set some key configurations required for supported functionality.
TEST_F(MacInterfaceTest, InstallKeysSupportConfigs) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
ASSERT_EQ(ZX_OK, NotifyAssociationComplete());
ASSERT_TRUE(mvmvif_->bss_conf.assoc);
fidl::Arena fidl_arena;
fidl::Array<uint8_t, 3> cipher_oui;
auto key =
fidl::VectorView<uint8_t>::FromExternal(const_cast<uint8_t*>(kFakeKey.begin()), kFakeKeyLen);
memcpy(cipher_oui.begin(), kIeeeOui, 3);
{
auto builder = fuchsia_wlan_softmac::wire::WlanKeyConfiguration::Builder(fidl_arena);
builder.key(key);
builder.cipher_oui(cipher_oui);
// Default cipher configuration for WPA2/3 PTK. This is data frame protection, required for
// WPA2/3.
builder.cipher_type(CIPHER_SUITE_TYPE_CCMP_128);
builder.key_type(fuchsia_wlan_common::wire::WlanKeyType::kPairwise);
builder.key_idx(0);
builder.rsc(0);
auto key_config = builder.Build();
ASSERT_EQ(ZX_OK, InstallKey(&key_config));
}
{
auto builder = fuchsia_wlan_softmac::wire::WlanKeyConfiguration::Builder(fidl_arena);
// Default cipher configuration for WPA2/3 IGTK. This is management frame protection, optional
// for WPA2 and required for WPA3.
builder.key(key);
builder.cipher_oui(cipher_oui);
builder.cipher_type(CIPHER_SUITE_TYPE_BIP_CMAC_128);
builder.key_type(fuchsia_wlan_common::wire::WlanKeyType::kIgtk);
builder.key_idx(4);
builder.rsc(0);
auto key_config = builder.Build();
ASSERT_EQ(ZX_OK, InstallKey(&key_config));
}
ASSERT_EQ(ZX_OK, ClearAssociation());
}
// Test setting TKIP. Mainly for group key (backward-compatible for many APs).
TEST_F(MacInterfaceTest, InstallKeysTKIP) {
constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, JoinBssRequest());
ASSERT_EQ(ZX_OK, NotifyAssociationComplete());
ASSERT_TRUE(mvmvif_->bss_conf.assoc);
fidl::Arena fidl_arena;
fidl::Array<uint8_t, 3> cipher_oui;
auto tkip_key = fidl::VectorView<uint8_t>::FromExternal(
const_cast<uint8_t*>(kFakeTkipKey.begin()), kFakeTkipKeyLen);
memcpy(cipher_oui.begin(), kIeeeOui, 3);
{
auto builder = fuchsia_wlan_softmac::wire::WlanKeyConfiguration::Builder(fidl_arena);
builder.key(tkip_key);
builder.cipher_oui(cipher_oui);
// TKIP Pairwise: although we support it but not recommended (deprecated protocol).
builder.cipher_type(fidl::ToUnderlying(fuchsia_wlan_ieee80211::wire::CipherSuiteType::kTkip));
builder.key_type(fuchsia_wlan_common::wire::WlanKeyType::kPairwise);
builder.key_idx(0);
builder.rsc(0);
auto key_config = builder.Build();
ASSERT_EQ(ZX_OK, InstallKey(&key_config));
}
{
// TKIP Group key: supported for backward compatible. Unfortunately many APs still use this.
auto builder = fuchsia_wlan_softmac::wire::WlanKeyConfiguration::Builder(fidl_arena);
builder.key(tkip_key);
builder.cipher_oui(cipher_oui);
builder.cipher_type(fidl::ToUnderlying(fuchsia_wlan_ieee80211::wire::CipherSuiteType::kTkip));
builder.key_type(fuchsia_wlan_common::wire::WlanKeyType::kIgtk);
builder.key_idx(1);
builder.rsc(0);
auto key_config = builder.Build();
ASSERT_EQ(ZX_OK, InstallKey(&key_config));
}
ASSERT_EQ(ZX_OK, ClearAssociation());
}
TEST_F(MacInterfaceTest, TxPktTooLong) {
SetChannel(&kChannel);
JoinBssRequest();
BIND_TEST(sim_trans_.iwl_trans());
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build_oversize();
fuchsia_wlan_softmac::wire::WlanTxPacket fidl_packet = wlan_pkt->wlan_pkt();
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_FROM_MLME), 0);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_TO_FW), 0);
auto result = client_.buffer(test_arena_)->QueueTx(fidl_packet);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_FROM_MLME), 1);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_TO_FW), 0);
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
EXPECT_EQ(ZX_ERR_INVALID_ARGS, result->error_value());
unbindTx();
}
TEST_F(MacInterfaceTest, TxPktNotSupportedRole) {
SetChannel(&kChannel);
JoinBssRequest();
BIND_TEST(sim_trans_.iwl_trans());
// Set to an unsupported role.
mvmvif_->mac_role = WLAN_MAC_ROLE_AP;
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
fuchsia_wlan_softmac::wire::WlanTxPacket fidl_packet = wlan_pkt->wlan_pkt();
auto result = client_.buffer(test_arena_)->QueueTx(fidl_packet);
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
EXPECT_EQ(ZX_ERR_INVALID_ARGS, result->error_value());
unbindTx();
}
// To test if a packet can be sent out.
TEST_F(MacInterfaceTest, TxPkt) {
SetChannel(&kChannel);
JoinBssRequest();
BIND_TEST(sim_trans_.iwl_trans());
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len(), WIDE_ID(0, TX_CMD), IWL_MVM_DQA_MIN_MGMT_QUEUE);
fuchsia_wlan_softmac::wire::WlanTxPacket fidl_packet = wlan_pkt->wlan_pkt();
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_FROM_MLME), 0);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_TO_FW), 0);
auto result = client_.buffer(test_arena_)->QueueTx(fidl_packet);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_FROM_MLME), 1);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_DATA_TO_FW), 1);
ASSERT_TRUE(result.ok());
EXPECT_FALSE(result->is_error());
unbindTx();
}
} // namespace
} // namespace wlan::testing