blob: 18e43beb936778d1f9b94e08f50c266570ac8267 [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 <gtest/gtest.h>
#include "../minstrel.h"
#include "../probe_sequence.h"
#include <fbl/algorithm.h>
#include <lib/timekeeper/test_clock.h>
#include <test_timer.h> // //garnet/lib/wlan/mlme/tests
#include <wlan/mlme/timer.h>
#include <wlan/mlme/timer_manager.h>
#include <wlan/protocol/info.h>
namespace wlan {
namespace {
namespace wlan_minstrel = ::fuchsia::wlan::minstrel;
ProbeSequence::ProbeTable SequentialTable() {
ProbeSequence::ProbeTable sequence_table;
for (uint8_t i = 0; i < ProbeSequence::kNumProbeSequece; ++i) {
for (tx_vec_idx_t j = kStartIdx; j <= kMaxValidIdx; ++j) {
sequence_table[i][j - kStartIdx] = j;
}
}
return sequence_table;
}
static const common::MacAddr kTestMacAddr({50, 53, 51, 56, 55, 52});
static const uint8_t kBasicRateBit = 0b10000000;
struct MinstrelTest : public ::testing::Test {
MinstrelTest()
: minstrel_(MinstrelRateSelector(fbl::make_unique<TestTimer>(0, &clock),
ProbeSequence(SequentialTable()), zx::msec(100))) {
kTestMacAddr.CopyTo(assoc_ctx_ht_.bssid);
}
void AdvanceTimeBy(zx::duration duration) { clock.Set(clock.Now() + duration); }
timekeeper::TestClock clock;
MinstrelRateSelector minstrel_;
wlan_assoc_ctx_t assoc_ctx_ht_{
.rates_cnt = 12,
.rates = {2, 4, 11, 22, 12 | kBasicRateBit, 18, 24, 36, 48, 72, 96, 108 | kBasicRateBit},
.has_ht_cap = true,
.ht_cap =
{
// left->right: SGI 40 MHz, SGI 20 MHz, 40 MHz
.ht_capability_info = 0b01100010,
.supported_mcs_set =
{
0xff, // MCS 0-7
0xff, // MCS 8-15
},
},
};
// Note: 16 is the max throughput and 136 is the highest basic rate. They will never be probed.
const tx_vec_idx_t want_probe_idx_[22] =
// clang-format off
{1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, /* 16 ,*/
129, 130, 131, 132, 133, 134, 135, /* 136 */};
// clang-format on
};
TEST_F(MinstrelTest, AddPeer) {
minstrel_.AddPeer(assoc_ctx_ht_);
EXPECT_TRUE(minstrel_.IsActive());
wlan_minstrel::Peers peers;
zx_status_t status = minstrel_.GetListToFidl(&peers);
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(1ULL, peers.peers.size());
wlan_minstrel::Peer peer;
status = minstrel_.GetStatsToFidl(kTestMacAddr, &peer);
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(kTestMacAddr, common::MacAddr(peer.mac_addr.data()));
// TODO(eyw): size would be 40 if 40 MHz is supported, 72 if 40 MHz and SGI are both supported.
EXPECT_EQ(24ULL, peer.entries.size());
EXPECT_EQ(16, peer.max_tp);
EXPECT_EQ(peer.entries[0].tx_vector_idx, peer.max_probability);
EXPECT_EQ(kErpStartIdx + kErpNumTxVector - 1, peer.basic_highest);
EXPECT_EQ(kErpStartIdx + kErpNumTxVector - 1, peer.basic_max_probability);
}
TEST_F(MinstrelTest, RemovePeer) {
// Add a peer to be removed later.
minstrel_.AddPeer(assoc_ctx_ht_);
EXPECT_TRUE(minstrel_.IsActive());
wlan_minstrel::Peers peers;
zx_status_t status = minstrel_.GetListToFidl(&peers);
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(1ULL, peers.peers.size());
// Remove the peer using its mac address.
minstrel_.RemovePeer(kTestMacAddr);
EXPECT_FALSE(minstrel_.IsActive());
status = minstrel_.GetListToFidl(&peers);
EXPECT_EQ(ZX_OK, status);
EXPECT_TRUE(peers.peers.empty());
wlan_minstrel::Peer peer;
status = minstrel_.GetStatsToFidl(kTestMacAddr, &peer);
EXPECT_EQ(ZX_ERR_NOT_FOUND, status);
}
TEST_F(MinstrelTest, HandleTimeout) {
clock.Set(zx::time(0));
minstrel_.AddPeer(assoc_ctx_ht_);
AdvanceTimeBy(zx::msec(99));
EXPECT_FALSE(minstrel_.HandleTimeout());
AdvanceTimeBy(zx::msec(1));
EXPECT_TRUE(minstrel_.HandleTimeout());
}
TEST_F(MinstrelTest, UpdateStats) {
// .tx_status_entry contains up to 8 entries
// All entries except the last one indicates failed attempts.
// The last entry can be successful or unsuccessful based on .success
wlan_tx_status_t tx_status{
.success = true,
.tx_status_entry =
{
// HT, CBW20, GI 800 ns,
{16, 1}, // MCS 7, fail
{15, 1}, // MCS 6, fail
{14, 1}, // MCS 5, fail
{13, 1}, // MCS 4, succeed because |.success| is true
},
};
clock.Set(zx::time(0));
minstrel_.AddPeer(assoc_ctx_ht_);
kTestMacAddr.CopyTo(tx_status.peer_addr);
minstrel_.HandleTxStatusReport(tx_status);
wlan_minstrel::Peer peer;
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
// tx_status collected but NOT been processed yet
// it will be processed every 100 ms, when HandleTimeout() is called.
EXPECT_EQ(16, peer.max_tp);
EXPECT_EQ(peer.entries[0].tx_vector_idx, peer.max_probability);
AdvanceTimeBy(zx::msec(100));
EXPECT_TRUE(minstrel_.HandleTimeout()); // tx_status are processed at HandleTimeout()
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
EXPECT_EQ(13, peer.max_tp); // Everything above 45 has 0 success, thus 0 throughput
EXPECT_EQ(13, peer.max_probability); // 45 has 100% success rate
// 45 fails, but 41 (MCS 0) succeeds because |.success| is still true
wlan_tx_status_entry_t entries[WLAN_TX_STATUS_MAX_ENTRY]{{13, 1}, {9, 1}};
memcpy(tx_status.tx_status_entry, &entries, sizeof(tx_status.tx_status_entry));
// after every cycle, success rate of 45 decrease to 75% of it previous value,
// success rate of 41 stays at 100% because of continuous positive outcome
// After enough cycles, 45's (success_rate * theoretical_throughput) becomes lower than 41.
for (int i = 0; i < 10; ++i) {
minstrel_.HandleTxStatusReport(tx_status);
AdvanceTimeBy(zx::msec(100));
EXPECT_TRUE(minstrel_.HandleTimeout());
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
EXPECT_EQ(9, peer.max_probability);
}
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
EXPECT_EQ(9, peer.max_tp);
}
TEST_F(MinstrelTest, HtIsMyFavorite) {
wlan_tx_status_t failed_ht_tx_status {
.success = false,
.tx_status_entry =
{
// MCS 8-15 all fail
{kHtStartIdx + 15, 1},
{kHtStartIdx + 14, 1},
{kHtStartIdx + 13, 1},
{kHtStartIdx + 12, 1},
{kHtStartIdx + 11, 1},
{kHtStartIdx + 10, 1},
{kHtStartIdx + 9, 1},
{kHtStartIdx + 8, 1},
}
};
wlan_tx_status_t ht_tx_status{
.success = true,
.tx_status_entry =
{
// MCS 1-7 all fail, only MCS 0 succeeds.
{kHtStartIdx + 7, 1},
{kHtStartIdx + 6, 1},
{kHtStartIdx + 5, 1},
{kHtStartIdx + 4, 1},
{kHtStartIdx + 3, 1},
{kHtStartIdx + 2, 1},
{kHtStartIdx + 1, 1},
// Lowest HT MCS with success probability 11% == (1/9) note: 0.1f < 1 - 0.9f
{kHtStartIdx + 0, 9},
},
};
wlan_tx_status_t erp_tx_status {
.success = true,
.tx_status_entry =
{
// Highest ERP rate with success probability 100% == (1/1)
{kErpStartIdx + kErpNumTxVector - 1, 1},
},
};
clock.Set(zx::time(0));
minstrel_.AddPeer(assoc_ctx_ht_);
kTestMacAddr.CopyTo(failed_ht_tx_status.peer_addr);
kTestMacAddr.CopyTo(ht_tx_status.peer_addr);
kTestMacAddr.CopyTo(erp_tx_status.peer_addr);
minstrel_.HandleTxStatusReport(failed_ht_tx_status);
minstrel_.HandleTxStatusReport(ht_tx_status);
minstrel_.HandleTxStatusReport(erp_tx_status);
AdvanceTimeBy(zx::msec(100));
ASSERT_TRUE(minstrel_.HandleTimeout()); // tx_status are processed at HandleTimeout()
wlan_minstrel::Peer peer;
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
// HT is selected for max_tp even though it has lower throughput
EXPECT_EQ(kHtStartIdx, peer.max_tp);
// HT is selected for max_probability even though it has lower probability
EXPECT_EQ(kHtStartIdx, peer.max_probability);
}
std::unordered_set<tx_vec_idx_t> GetAllIndices(const wlan_minstrel::Peer& peer) {
std::unordered_set<tx_vec_idx_t> indices;
for (const auto& entry : peer.entries) {
indices.emplace(entry.tx_vector_idx);
}
return indices;
}
TEST_F(MinstrelTest, AddMissingTxVector) {
clock.Set(zx::time(0));
assoc_ctx_ht_.rates_cnt = 10;
const uint8_t fewer_rates[10] = {2, 4, 11, 22, 12, 18, 24, 36, 48, 72}; // missing 96 and 108
std::copy(std::cbegin(fewer_rates), std::cend(fewer_rates), assoc_ctx_ht_.rates);
minstrel_.AddPeer(assoc_ctx_ht_);
wlan_tx_status_t tx_status{
.success = true,
.tx_status_entry =
{
// ERP, CBW20, GI 800 ns,
{kErpStartIdx + kErpNumTxVector - 1, 1}, // MCS 7, 108, non-present, fail
{kErpStartIdx + kErpNumTxVector - 3, 1}, // MCS 5, 72, present, succeed
},
};
kTestMacAddr.CopyTo(tx_status.peer_addr);
wlan_minstrel::Peer peer;
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
auto indices = GetAllIndices(peer);
EXPECT_FALSE(indices.count(kErpStartIdx + kErpNumTxVector - 1));
minstrel_.HandleTxStatusReport(tx_status);
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
indices = GetAllIndices(peer);
EXPECT_TRUE(indices.count(kErpStartIdx + kErpNumTxVector - 1));
}
TEST_F(MinstrelTest, DataFramesEligibleForProbing) {
clock.Set(zx::time(0));
minstrel_.AddPeer(assoc_ctx_ht_);
wlan_minstrel::Peer peer;
EXPECT_EQ(ZX_OK, minstrel_.GetStatsToFidl(kTestMacAddr, &peer));
FrameControl fc;
fc.set_type(FrameType::kData);
for (tx_vec_idx_t i = 0; i < kProbeInterval * fbl::count_of(want_probe_idx_); ++i) {
const tx_vec_idx_t idx = minstrel_.GetTxVectorIdx(fc, kTestMacAddr, 0);
if (i % kProbeInterval == kProbeInterval - 1) {
tx_vec_idx_t want = want_probe_idx_[(i + 1) / kProbeInterval - 1];
if (want != idx) { printf("probe mismatch #%d\n", i); }
EXPECT_EQ(want, idx);
} else {
if (peer.max_tp != idx) { printf("non-probe mismatch #%d\n", i); }
EXPECT_EQ(peer.max_tp, idx);
}
}
}
} // namespace
} // namespace wlan