blob: 602530dfec76b069038aacc30a423215bccac7e3 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <wlan/common/element.h>
#include <wlan/mlme/client/station.h>
#include <fuchsia/wlan/mlme/cpp/fidl.h>
#include <lib/fidl/cpp/vector.h>
#include <wlan/common/buffer_writer.h>
#include <wlan/common/mac_frame.h>
#include <wlan/common/macaddr.h>
#include <wlan/common/span.h>
#include <wlan/common/write_element.h>
#include <wlan/mlme/assoc_context.h>
#include <wlan/mlme/packet.h>
#include <wlan/mlme/rates_elements.h>
#include <gtest/gtest.h>
#include <optional>
#include <vector>
#include "test_utils.h"
namespace wlan {
namespace {
struct TestVector {
std::vector<uint8_t> ap_basic_rate_set;
std::vector<uint8_t> ap_op_rate_set;
std::vector<SupportedRate> client_rates;
std::optional<std::vector<SupportedRate>> want_rates;
};
const common::MacAddr TestMac({0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
namespace wlan_mlme = ::fuchsia::wlan::mlme;
using SR = SupportedRate;
constexpr auto SR_b = SupportedRate::basic;
// op_rate_set is a super set of basic_rate_set.
// Result is the intersection of ap_op_rate_set and client_rates.
// The basic-ness of client rates are disregarded and the basic-ness of AP is preserved.
void TestOnce(const TestVector& tv) {
auto basic = ::fidl::VectorPtr(tv.ap_basic_rate_set);
auto op = ::fidl::VectorPtr(tv.ap_op_rate_set);
auto got_rates = BuildAssocReqSuppRates(basic, op, tv.client_rates);
ASSERT_EQ(tv.want_rates.has_value(), got_rates.has_value());
if (!got_rates.has_value()) { return; }
EXPECT_EQ(tv.want_rates.value(), got_rates.value());
for (size_t i = 0; i < got_rates->size(); ++i) {
EXPECT_EQ(tv.want_rates.value()[i].val(), got_rates.value()[i].val());
}
}
AssociationResponse* WriteAssocRespHdr(BufferWriter* w) {
auto assoc_resp = w->Write<AssociationResponse>();
assoc_resp->aid = 1234;
assoc_resp->status_code = 2345;
return assoc_resp;
}
Span<const uint8_t> WriteAssocRespElements(Span<uint8_t> buffer) {
BufferWriter w(buffer);
HtCapabilities ht_cap{};
ht_cap.ht_cap_info.set_rx_stbc(1);
ht_cap.ht_cap_info.set_tx_stbc(1);
HtOperation ht_op{};
VhtCapabilities vht_cap{};
common::WriteHtCapabilities(&w, ht_cap);
common::WriteHtOperation(&w, ht_op);
common::WriteVhtCapabilities(&w, vht_cap);
return w.WrittenData();
}
TEST(AssociationRatesTest, Success) {
TestOnce({
.ap_basic_rate_set = {1},
.ap_op_rate_set = {1, 2},
.client_rates = {SR{1}, SR{2}, SR{3}},
.want_rates = {{SR_b(1), SR(2)}},
});
}
TEST(AssociationRatesTest, SuccessWithDuplicateRates) {
TestOnce({
.ap_basic_rate_set = {1, 1},
.ap_op_rate_set = {1},
.client_rates = {SR{1}, SR{2}, SR{3}},
.want_rates = {{SR_b(1)}},
});
}
TEST(AssociationRatesTest, FailureNoApBasicRatesSupported) {
TestOnce({
.ap_basic_rate_set = {1},
.ap_op_rate_set = {1},
.client_rates = {SR{2}, SR{3}},
.want_rates = {},
});
}
TEST(AssociationRatesTest, FailureApBasicRatesPartiallySupported) {
TestOnce({
.ap_basic_rate_set = {1, 4},
.ap_op_rate_set = {1, 4},
.client_rates = {SR{1}, SR{2}, SR{3}},
.want_rates = {},
});
}
TEST(ParseAssocRespIe, ParseToFail) {
const uint8_t expected[] = {
// clang-format off
// HT Capabilities IE
45, 26,
0xaa, 0xbb, 0x55, 0x0, 0x1, 0x2, 0x3, 0x4,
0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc,
0xd, 0xe, 0xf, 0xdd, 0xee, 0x11, 0x22, 0x33,
0x44, 0x77,
// HT Operation IE
61, 20, // (61, 20) is a corrupted value pair. Valid pair is (61, 22)
36, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0, 0x1,
0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9,
0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
// clang-format on
};
auto ctx = ParseAssocRespIe(expected);
EXPECT_FALSE(ctx.has_value());
}
TEST(ParseAssocRespIe, Parse) {
size_t kBufLen = 512;
std::vector<uint8_t> ie_chains(kBufLen);
SupportedRate rates[3] = {SupportedRate{10}, SupportedRate{20}, SupportedRate{30}};
HtCapabilities ht_cap{};
HtOperation ht_op{};
VhtCapabilities vht_cap{};
VhtOperation vht_op{};
ht_cap.ht_cap_info.set_rx_stbc(1);
ht_cap.ht_cap_info.set_tx_stbc(0);
ht_op.primary_chan = 199;
ht_op.head.set_center_freq_seg2(123);
vht_cap.vht_cap_info.set_num_sounding(5);
vht_op.center_freq_seg0 = 42;
BufferWriter elem_w(ie_chains);
common::WriteHtCapabilities(&elem_w, ht_cap);
common::WriteVhtOperation(&elem_w, vht_op);
common::WriteHtOperation(&elem_w, ht_op);
RatesWriter(rates).WriteSupportedRates(&elem_w);
common::WriteVhtCapabilities(&elem_w, vht_cap);
auto ctx = ParseAssocRespIe(elem_w.WrittenData());
ASSERT_TRUE(ctx.has_value());
EXPECT_EQ(rates[0], ctx->rates[0]);
EXPECT_EQ(rates[1], ctx->rates[1]);
EXPECT_EQ(rates[2], ctx->rates[2]);
EXPECT_EQ(1, ctx->ht_cap->ht_cap_info.rx_stbc());
EXPECT_EQ(0, ctx->ht_cap->ht_cap_info.tx_stbc());
EXPECT_EQ(199, ctx->ht_op->primary_chan);
EXPECT_EQ(123, ctx->ht_op->head.center_freq_seg2());
EXPECT_EQ(5, ctx->vht_cap->vht_cap_info.num_sounding());
EXPECT_EQ(42, ctx->vht_op->center_freq_seg0);
}
TEST(AssocContext, IntersectHtNoVht) {
// Constructing client and BSS sample association context without VHT.
auto bss_ctx = test_utils::FakeAssocCtx();
bss_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
bss_ctx.ht_cap->ht_cap_info.set_rx_stbc(1);
bss_ctx.ht_cap->ht_cap_info.set_tx_stbc(0);
bss_ctx.vht_cap = {};
bss_ctx.vht_op = {};
auto client_ctx = test_utils::FakeAssocCtx();
client_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
client_ctx.ht_cap->ht_cap_info.set_rx_stbc(1);
client_ctx.ht_cap->ht_cap_info.set_tx_stbc(0);
client_ctx.vht_cap = {};
client_ctx.vht_op = {};
auto ctx = IntersectAssocCtx(bss_ctx, client_ctx);
// Verify VHT is not part of resulting context.
EXPECT_EQ(std::nullopt, ctx.vht_cap);
EXPECT_EQ(std::nullopt, ctx.vht_op);
// Verify context's other fields contain expected value.
EXPECT_TRUE(ctx.ht_cap.has_value());
EXPECT_TRUE(ctx.ht_op.has_value());
EXPECT_EQ(0, ctx.ht_cap->ht_cap_info.tx_stbc());
EXPECT_EQ(0, ctx.ht_cap->ht_cap_info.rx_stbc());
EXPECT_TRUE(ctx.is_cbw40_rx);
EXPECT_FALSE(ctx.is_cbw40_tx); // TODO(NET-1918): Revisit with rx/tx CBW40 capability
}
TEST(AssocContext, IntersectClientNoHT) {
auto bss_ctx = test_utils::FakeAssocCtx();
bss_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
auto client_ctx = test_utils::FakeAssocCtx();
client_ctx.ht_cap = {};
client_ctx.vht_cap = {};
client_ctx.vht_op = {};
auto ctx = IntersectAssocCtx(bss_ctx, client_ctx);
EXPECT_FALSE(ctx.ht_cap.has_value());
EXPECT_FALSE(ctx.vht_cap.has_value());
EXPECT_FALSE(ctx.vht_op.has_value());
}
TEST(AssocContext, IntersectHtVht) {
auto bss_ctx = test_utils::FakeAssocCtx();
bss_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
auto client_ctx = test_utils::FakeAssocCtx();
client_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
auto ctx = IntersectAssocCtx(bss_ctx, client_ctx);
EXPECT_TRUE(ctx.vht_cap.has_value());
EXPECT_TRUE(ctx.vht_op.has_value());
}
TEST(AssocContext, IntersectClientNoVht) {
auto bss_ctx = test_utils::FakeAssocCtx();
bss_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
auto client_ctx = test_utils::FakeAssocCtx();
client_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
client_ctx.vht_cap = {};
auto ctx = IntersectAssocCtx(bss_ctx, client_ctx);
EXPECT_TRUE(ctx.ht_cap.has_value());
EXPECT_TRUE(ctx.ht_op.has_value());
EXPECT_FALSE(ctx.vht_cap.has_value());
EXPECT_FALSE(ctx.vht_op.has_value());
}
TEST(AssocContext, IntersectBssNoHT) {
auto bss_ctx = test_utils::FakeAssocCtx();
bss_ctx.ht_cap = {};
bss_ctx.ht_op = {};
bss_ctx.vht_cap = {};
bss_ctx.vht_op = {};
auto client_ctx = test_utils::FakeAssocCtx();
client_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
auto ctx = IntersectAssocCtx(bss_ctx, client_ctx);
EXPECT_FALSE(ctx.ht_cap.has_value());
EXPECT_FALSE(ctx.ht_op.has_value());
EXPECT_FALSE(ctx.vht_cap.has_value());
EXPECT_FALSE(ctx.vht_op.has_value());
}
TEST(AssocContext, IntersectBssNoVht) {
auto bss_ctx = test_utils::FakeAssocCtx();
bss_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
bss_ctx.vht_cap = {};
bss_ctx.vht_op = {};
auto client_ctx = test_utils::FakeAssocCtx();
client_ctx.ht_cap->ht_cap_info.set_chan_width_set(HtCapabilityInfo::TWENTY_FORTY);
auto ctx = IntersectAssocCtx(bss_ctx, client_ctx);
EXPECT_TRUE(ctx.ht_cap.has_value());
EXPECT_TRUE(ctx.ht_op.has_value());
EXPECT_FALSE(ctx.vht_cap.has_value());
EXPECT_FALSE(ctx.vht_op.has_value());
}
TEST(AssocContext, MakeBssAssocCtx) {
uint8_t buffer[512]{};
BufferWriter w(buffer);
auto assoc_resp = WriteAssocRespHdr(&w);
auto ie_chain = WriteAssocRespElements(w.RemainingBuffer());
auto ctx = MakeBssAssocCtx(*assoc_resp, ie_chain, TestMac);
ASSERT_TRUE(ctx.has_value());
ASSERT_TRUE(ctx->ht_cap.has_value());
EXPECT_TRUE(ctx->ht_op.has_value());
EXPECT_TRUE(ctx->vht_cap.has_value());
EXPECT_FALSE(ctx->vht_op.has_value());
EXPECT_EQ(1, ctx->ht_cap->ht_cap_info.rx_stbc());
EXPECT_EQ(1, ctx->ht_cap->ht_cap_info.tx_stbc());
}
TEST(AssocContext, ToDdk) {
// TODO(NET-1959): Test more fields.
auto ctx = test_utils::FakeAssocCtx();
ctx.vht_cap = {};
ctx.vht_op = {};
auto ddk = ctx.ToDdk();
EXPECT_TRUE(ddk.has_ht_cap);
EXPECT_TRUE(ddk.has_ht_op);
EXPECT_FALSE(ddk.has_vht_cap);
EXPECT_FALSE(ddk.has_vht_op);
}
} // namespace
} // namespace wlan