| // 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/mlme/ap/ap_mlme.h> |
| #include <wlan/mlme/ap/beacon_sender.h> |
| #include <wlan/mlme/mac_frame.h> |
| #include <wlan/mlme/packet.h> |
| #include <wlan/mlme/service.h> |
| #include <wlan/mlme/timer.h> |
| |
| #include <fbl/unique_ptr.h> |
| #include <fuchsia/wlan/mlme/c/fidl.h> |
| #include <fuchsia/wlan/mlme/cpp/fidl.h> |
| |
| #include "mock_device.h" |
| #include "test_bss.h" |
| #include "test_utils.h" |
| |
| #include <gtest/gtest.h> |
| #include <vector> |
| |
| namespace wlan { |
| |
| namespace { |
| |
| namespace wlan_mlme = ::fuchsia::wlan::mlme; |
| |
| constexpr uint8_t kTestPayload[] = "Hello Fuchsia"; |
| |
| void AssertSendRate(WlanPacket pkt, CBW cbw, PHY phy, uint32_t flags) { |
| EXPECT_EQ(pkt.cbw, cbw); |
| EXPECT_EQ(pkt.phy, phy); |
| EXPECT_EQ(pkt.flags, flags); |
| } |
| |
| struct Context { |
| Context(MockDevice* device, ApMlme* ap, const common::MacAddr& client_addr) |
| : device(device), ap(ap), client_addr(client_addr) {} |
| |
| void HandleTimeout() { |
| ObjectId timer_id; |
| timer_id.set_subtype(to_enum_type(ObjectSubtype::kTimer)); |
| timer_id.set_target(to_enum_type(ObjectTarget::kBss)); |
| timer_id.set_mac(client_addr.ToU64()); |
| ap->HandleTimeout(timer_id); |
| } |
| |
| template <typename FV> FV TypeCheckWlanFrame(Packet* pkt) { |
| EXPECT_EQ(pkt->peer(), Packet::Peer::kWlan); |
| auto type_checked_frame = FV::CheckType(pkt); |
| EXPECT_TRUE(type_checked_frame); |
| auto frame = type_checked_frame.CheckLength(); |
| EXPECT_TRUE(frame); |
| return frame; |
| } |
| |
| void SendClientAuthReqFrame() { ap->HandleFramePacket(CreateAuthReqFrame(client_addr)); } |
| |
| void SendClientDeauthFrame() { ap->HandleFramePacket(CreateDeauthFrame(client_addr)); } |
| |
| void SendClientAssocReqFrame(Span<const uint8_t> ssid = kSsid, bool rsn = true) { |
| ap->HandleFramePacket(CreateAssocReqFrame(client_addr, ssid, rsn)); |
| } |
| |
| void SendClientDisassocFrame() { ap->HandleFramePacket(CreateDisassocFrame(client_addr)); } |
| |
| void SendNullDataFrame(bool pwr_mgmt) { |
| auto frame = CreateNullDataFrame(); |
| common::MacAddr bssid(kBssid1); |
| frame.hdr()->fc.set_from_ds(0); |
| frame.hdr()->fc.set_to_ds(1); |
| frame.hdr()->fc.set_pwr_mgmt(pwr_mgmt ? 1 : 0); |
| frame.hdr()->addr1 = bssid; |
| frame.hdr()->addr2 = client_addr; |
| frame.hdr()->addr3 = bssid; |
| ap->HandleFramePacket(frame.Take()); |
| } |
| |
| void SendDataFrame(Span<const uint8_t> payload) { |
| auto pkt = CreateDataFrame(payload); |
| auto hdr = pkt->mut_field<DataFrameHeader>(0); |
| common::MacAddr bssid(kBssid1); |
| hdr->fc.set_from_ds(0); |
| hdr->fc.set_to_ds(1); |
| hdr->addr1 = bssid; |
| hdr->addr2 = client_addr; |
| hdr->addr3 = bssid; |
| ap->HandleFramePacket(std::move(pkt)); |
| } |
| |
| void SendEthFrame(Span<const uint8_t> payload) { |
| auto pkt = CreateEthFrame(payload); |
| auto hdr = pkt->mut_field<EthernetII>(0); |
| hdr->src = common::MacAddr(kBssid1); |
| hdr->dest = client_addr; |
| ap->HandleFramePacket(std::move(pkt)); |
| } |
| |
| zx::duration TuPeriodsToDuration(size_t periods) { return zx::usec(1024) * periods; } |
| |
| void SetTimeInTuPeriods(size_t periods) { |
| device->SetTime(zx::time(0) + TuPeriodsToDuration(periods)); |
| } |
| |
| void StartAp(bool protected_ap = true) { |
| ap->HandleMlmeMsg(CreateStartRequest(protected_ap)); |
| device->svc_queue.clear(); |
| device->wlan_queue.clear(); |
| } |
| |
| void AuthenticateClient() { |
| SendClientAuthReqFrame(); |
| ap->HandleMlmeMsg( |
| CreateAuthResponse(client_addr, wlan_mlme::AuthenticateResultCodes::SUCCESS)); |
| device->svc_queue.clear(); |
| device->wlan_queue.clear(); |
| } |
| |
| void AssociateClient(uint16_t aid) { |
| SendClientAssocReqFrame(); |
| ap->HandleMlmeMsg( |
| CreateAssocResponse(client_addr, wlan_mlme::AssociateResultCodes::SUCCESS, aid)); |
| device->svc_queue.clear(); |
| device->wlan_queue.clear(); |
| } |
| |
| void AuthenticateAndAssociateClient(uint16_t aid) { |
| AuthenticateClient(); |
| AssociateClient(aid); |
| } |
| |
| void EstablishRsna() { |
| ap->HandleMlmeMsg( |
| CreateSetCtrlPortRequest(client_addr, wlan_mlme::ControlledPortState::OPEN)); |
| } |
| |
| void AssertAuthInd(MlmeMsg<wlan_mlme::AuthenticateIndication> msg) { |
| EXPECT_EQ(std::memcmp(msg.body()->peer_sta_address.data(), client_addr.byte, 6), 0); |
| EXPECT_EQ(msg.body()->auth_type, wlan_mlme::AuthenticationTypes::OPEN_SYSTEM); |
| } |
| |
| void AssertDeauthInd(MlmeMsg<wlan_mlme::DeauthenticateIndication> msg, |
| wlan_mlme::ReasonCode reason_code) { |
| EXPECT_EQ(std::memcmp(msg.body()->peer_sta_address.data(), client_addr.byte, 6), 0); |
| EXPECT_EQ(msg.body()->reason_code, reason_code); |
| } |
| |
| void AssertAssocInd(MlmeMsg<wlan_mlme::AssociateIndication> msg, bool rsn = true) { |
| EXPECT_EQ(std::memcmp(msg.body()->peer_sta_address.data(), client_addr.byte, 6), 0); |
| EXPECT_EQ(msg.body()->listen_interval, kListenInterval); |
| EXPECT_EQ(std::memcmp(msg.body()->ssid->data(), kSsid, msg.body()->ssid->size()), 0); |
| if (rsn) { |
| EXPECT_EQ(std::memcmp(msg.body()->rsn->data(), kRsne, sizeof(kRsne)), 0); |
| } else { |
| EXPECT_TRUE(msg.body()->rsn.is_null()); |
| } |
| } |
| |
| void AssertDisassocInd(MlmeMsg<wlan_mlme::DisassociateIndication> msg) { |
| EXPECT_EQ(std::memcmp(msg.body()->peer_sta_address.data(), client_addr.byte, 6), 0); |
| EXPECT_EQ(msg.body()->reason_code, |
| static_cast<uint16_t>(wlan_mlme::ReasonCode::LEAVING_NETWORK_DISASSOC)); |
| } |
| |
| void AssertAuthFrame(WlanPacket pkt) { |
| auto frame = TypeCheckWlanFrame<MgmtFrameView<Authentication>>(pkt.pkt.get()); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.body()->auth_algorithm_number, AuthAlgorithm::kOpenSystem); |
| EXPECT_EQ(frame.body()->auth_txn_seq_number, 2); |
| EXPECT_EQ(frame.body()->status_code, status_code::kSuccess); |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_OFDM, 0); |
| } |
| |
| void AssertAssocFrame(WlanPacket pkt) { |
| auto frame = TypeCheckWlanFrame<MgmtFrameView<AssociationResponse>>(pkt.pkt.get()); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.body()->status_code, status_code::kSuccess); |
| EXPECT_EQ(frame.body()->aid, kAid); |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_OFDM, 0); |
| } |
| |
| struct DataFrameAssert { |
| unsigned char protected_frame = 0; |
| unsigned char more_data = 0; |
| }; |
| |
| void AssertDataFrameSentToClient(WlanPacket pkt, Span<const uint8_t> expected_payload, |
| DataFrameAssert asserts = {.protected_frame = 0, |
| .more_data = 0}) { |
| auto frame = TypeCheckWlanFrame<DataFrameView<LlcHeader>>(pkt.pkt.get()); |
| ASSERT_TRUE(frame); |
| EXPECT_EQ(frame.hdr()->fc.more_data(), asserts.more_data); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.hdr()->fc.protected_frame(), asserts.protected_frame); |
| |
| auto llc_frame = frame.NextFrame(); |
| EXPECT_RANGES_EQ(llc_frame.body_data(), expected_payload); |
| |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_HT, 0); |
| } |
| |
| void AssertEthFrame(Span<const uint8_t> pkt, Span<const uint8_t> expected_payload) { |
| BufferReader rdr(pkt); |
| auto hdr = rdr.Read<EthernetII>(); |
| ASSERT_NE(hdr, nullptr); |
| EXPECT_EQ(std::memcmp(hdr->src.byte, client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(hdr->dest.byte, kBssid1, 6), 0); |
| EXPECT_EQ(hdr->ether_type, 42); |
| auto payload = rdr.ReadRemaining(); |
| EXPECT_RANGES_EQ(payload, expected_payload); |
| } |
| |
| MockDevice* device; |
| ApMlme* ap; |
| common::MacAddr client_addr; |
| }; |
| |
| struct ApInfraBssTest : public ::testing::Test { |
| ApInfraBssTest() |
| : device(common::MacAddr(kBssid1)), |
| ap(&device), |
| ctx(&device, &ap, common::MacAddr(kClientAddress)) {} |
| |
| void SetUp() override { device.SetTime(zx::time(0)); } |
| void TearDown() override { ap.HandleMlmeMsg(CreateStopRequest()); } |
| |
| MockDevice device; |
| ApMlme ap; |
| Context ctx; |
| }; |
| |
| TEST_F(ApInfraBssTest, StartAp) { |
| ctx.ap->HandleMlmeMsg(CreateStartRequest(true)); |
| |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto start_confs = |
| device.GetServiceMsgs<wlan_mlme::StartConfirm>(fuchsia_wlan_mlme_MLMEStartConfOrdinal); |
| ASSERT_EQ(start_confs.size(), 1ULL); |
| ASSERT_EQ(start_confs[0].body()->result_code, wlan_mlme::StartResultCodes::SUCCESS); |
| } |
| |
| TEST_F(ApInfraBssTest, Authenticate_Success) { |
| ctx.StartAp(); |
| |
| // Send authentication request frame |
| ctx.SendClientAuthReqFrame(); |
| |
| // Verify that an Authentication.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto auth_inds = device.GetServiceMsgs<wlan_mlme::AuthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEAuthenticateIndOrdinal); |
| ASSERT_EQ(auth_inds.size(), 1ULL); |
| ctx.AssertAuthInd(std::move(auth_inds[0])); |
| |
| // Simulate SME sending MLME-AUTHENTICATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg( |
| CreateAuthResponse(ctx.client_addr, wlan_mlme::AuthenticateResultCodes::SUCCESS)); |
| |
| // Verify authentication response frame for the client |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| ctx.AssertAuthFrame(std::move(*device.wlan_queue.begin())); |
| } |
| |
| TEST_F(ApInfraBssTest, Authenticate_SmeRefuses) { |
| ctx.StartAp(); |
| |
| // Send authentication request frame |
| ctx.SendClientAuthReqFrame(); |
| |
| // Simulate SME sending MLME-AUTHENTICATE.response msg with a refusal code |
| ctx.ap->HandleMlmeMsg( |
| CreateAuthResponse(ctx.client_addr, wlan_mlme::AuthenticateResultCodes::REFUSED)); |
| |
| // Verify that authentication response frame for client is a refusal |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| auto frame = ctx.TypeCheckWlanFrame<MgmtFrameView<Authentication>>(pkt.pkt.get()); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, ctx.client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.body()->auth_algorithm_number, AuthAlgorithm::kOpenSystem); |
| EXPECT_EQ(frame.body()->auth_txn_seq_number, 2); |
| EXPECT_EQ(frame.body()->status_code, status_code::kRefused); |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_OFDM, 0); |
| } |
| |
| TEST_F(ApInfraBssTest, Authenticate_Timeout) { |
| ctx.StartAp(); |
| |
| // Send authentication request frame |
| ctx.SendClientAuthReqFrame(); |
| device.svc_queue.clear(); |
| |
| // No timeout yet, so nothing happens. Even if another auth request comes, it's a no-op |
| ctx.SetTimeInTuPeriods(59000); |
| ctx.HandleTimeout(); |
| ctx.SendClientAuthReqFrame(); |
| EXPECT_TRUE(device.svc_queue.empty()); |
| EXPECT_TRUE(device.wlan_queue.empty()); |
| |
| // Timeout triggers. Verify that if another auth request comes, it's processed. |
| ctx.SetTimeInTuPeriods(60000); |
| ctx.HandleTimeout(); |
| ctx.SendClientAuthReqFrame(); |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| EXPECT_TRUE(device.wlan_queue.empty()); |
| auto auth_inds = device.GetServiceMsgs<wlan_mlme::AuthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEAuthenticateIndOrdinal); |
| ASSERT_EQ(auth_inds.size(), 1ULL); |
| ctx.AssertAuthInd(std::move(auth_inds[0])); |
| } |
| |
| TEST_F(ApInfraBssTest, ReauthenticateWhileAuthenticated) { |
| ctx.StartAp(); |
| ctx.AuthenticateClient(); |
| |
| // Send authentication request frame |
| ctx.SendClientAuthReqFrame(); |
| |
| // Verify that an Authentication.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto auth_inds = device.GetServiceMsgs<wlan_mlme::AuthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEAuthenticateIndOrdinal); |
| ASSERT_EQ(auth_inds.size(), 1ULL); |
| ctx.AssertAuthInd(std::move(auth_inds[0])); |
| |
| // Simulate SME sending MLME-AUTHENTICATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg( |
| CreateAuthResponse(ctx.client_addr, wlan_mlme::AuthenticateResultCodes::SUCCESS)); |
| |
| // Verify authentication response frame for the client |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| ctx.AssertAuthFrame(std::move(*device.wlan_queue.begin())); |
| } |
| |
| TEST_F(ApInfraBssTest, DeauthenticateWhileAuthenticated) { |
| ctx.StartAp(); |
| ctx.AuthenticateClient(); |
| |
| // Send deauthentication frame |
| ctx.SendClientDeauthFrame(); |
| |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| ctx.AssertDeauthInd(std::move(deauth_inds[0]), wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| |
| // Expect association context is still blank. |
| wlan_assoc_ctx_t expected_ctx = {}; |
| const wlan_assoc_ctx_t* actual_ctx = device.GetStationAssocContext(); |
| EXPECT_EQ(std::memcmp(actual_ctx, &expected_ctx, sizeof(expected_ctx)), 0); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_Success) { |
| ctx.StartAp(); |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame |
| ctx.SendClientAssocReqFrame(); |
| |
| // Verify that an Association.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto assoc_inds = device.GetServiceMsgs<wlan_mlme::AssociateIndication>( |
| fuchsia_wlan_mlme_MLMEAssociateIndOrdinal); |
| ASSERT_EQ(assoc_inds.size(), 1ULL); |
| ctx.AssertAssocInd(std::move(assoc_inds[0])); |
| |
| // Simulate SME sending MLME-ASSOCIATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg( |
| CreateAssocResponse(ctx.client_addr, wlan_mlme::AssociateResultCodes::SUCCESS, kAid)); |
| |
| // Verify association response frame for the client |
| // WLAN queue should have AssociateResponse and BlockAck request |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(2)); |
| ctx.AssertAssocFrame(std::move(*device.wlan_queue.begin())); |
| |
| device.wlan_queue.clear(); |
| ctx.SendEthFrame(kTestPayload); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_AssociationContext) { |
| ctx.StartAp(); |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame |
| ctx.SendClientAssocReqFrame(); |
| |
| // Simulate SME sending MLME-ASSOCIATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg( |
| CreateAssocResponse(ctx.client_addr, wlan_mlme::AssociateResultCodes::SUCCESS, kAid)); |
| |
| // Expect association context has been set properly. |
| const wlan_assoc_ctx_t* actual_ctx = device.GetStationAssocContext(); |
| EXPECT_EQ(std::memcmp(actual_ctx->bssid, ctx.client_addr.byte, 6), 0); |
| EXPECT_EQ(actual_ctx->aid, kAid); |
| auto rates = ctx.ap->Rates(); |
| EXPECT_EQ(actual_ctx->rates_cnt, rates.size()); |
| for (size_t i = 0; i < rates.size(); i++) { |
| EXPECT_EQ(actual_ctx->rates[i], rates[i]); |
| } |
| EXPECT_TRUE(actual_ctx->has_ht_cap); |
| const wlan_ht_caps_t expected_ht_cap = BuildHtCapabilities(ctx.ap->Ht()).ToDdk(); |
| const wlan_ht_caps_t actual_ht_cap = actual_ctx->ht_cap; |
| EXPECT_EQ(actual_ht_cap.ht_capability_info, expected_ht_cap.ht_capability_info); |
| EXPECT_EQ(actual_ht_cap.ampdu_params, expected_ht_cap.ampdu_params); |
| size_t len = |
| sizeof(expected_ht_cap.supported_mcs_set) / sizeof(expected_ht_cap.supported_mcs_set[0]); |
| for (size_t i = 0; i < len; i++) { |
| EXPECT_EQ(actual_ht_cap.supported_mcs_set[i], expected_ht_cap.supported_mcs_set[i]); |
| } |
| EXPECT_EQ(actual_ht_cap.ht_ext_capabilities, expected_ht_cap.ht_ext_capabilities); |
| EXPECT_EQ(actual_ht_cap.tx_beamforming_capabilities, |
| expected_ht_cap.tx_beamforming_capabilities); |
| EXPECT_EQ(actual_ht_cap.asel_capabilities, expected_ht_cap.asel_capabilities); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_MultipleClients) { |
| ctx.StartAp(); |
| Context client2_ctx(&device, &ap, common::MacAddr({0x22, 0x22, 0x22, 0x22, 0x22, 0x22})); |
| |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Eth frame from client 2 is no-op since client 2 is not associated |
| client2_ctx.SendEthFrame(kTestPayload); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| uint16_t client2_aid = 5; |
| client2_ctx.AuthenticateAndAssociateClient(client2_aid); |
| |
| // Test sending message to client 1 |
| ctx.SendEthFrame(kTestPayload); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload); |
| device.wlan_queue.clear(); |
| |
| // Test sending message to client 2 |
| client2_ctx.SendEthFrame(kTestPayload); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| pkt = std::move(*device.wlan_queue.begin()); |
| client2_ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_SmeRefuses) { |
| ctx.StartAp(); |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame |
| ctx.SendClientAssocReqFrame(); |
| |
| // Simulate SME sending MLME-ASSOCIATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg(CreateAssocResponse( |
| ctx.client_addr, wlan_mlme::AssociateResultCodes::REFUSED_CAPABILITIES_MISMATCH, 0)); |
| |
| // Expect association context has not been set (blank). |
| wlan_assoc_ctx_t expected_ctx = {}; |
| const wlan_assoc_ctx_t* actual_ctx = device.GetStationAssocContext(); |
| EXPECT_EQ(std::memcmp(actual_ctx, &expected_ctx, sizeof(expected_ctx)), 0); |
| |
| // Verify association response frame for the client is a refusal |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| auto frame = ctx.TypeCheckWlanFrame<MgmtFrameView<AssociationResponse>>(pkt.pkt.get()); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, ctx.client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.body()->status_code, status_code::kRefusedCapabilitiesMismatch); |
| EXPECT_EQ(frame.body()->aid, 0); |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_OFDM, 0); |
| |
| device.wlan_queue.clear(); |
| // Sending frame should be a no-op since association fails |
| ctx.SendEthFrame(kTestPayload); |
| EXPECT_TRUE(device.wlan_queue.empty()); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_Timeout) { |
| ctx.StartAp(); |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame |
| ctx.SendClientAssocReqFrame(); |
| device.svc_queue.clear(); |
| |
| // No timeout yet, so nothing happens. Even if another assoc request comes, it's a no-op |
| ctx.SetTimeInTuPeriods(59000); |
| ctx.HandleTimeout(); |
| ctx.SendClientAssocReqFrame(); |
| EXPECT_TRUE(device.svc_queue.empty()); |
| EXPECT_TRUE(device.wlan_queue.empty()); |
| |
| // Timeout triggers. Verify that if another assoc request comes, it's processed. |
| ctx.SetTimeInTuPeriods(60000); |
| ctx.HandleTimeout(); |
| ctx.SendClientAssocReqFrame(); |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| EXPECT_TRUE(device.wlan_queue.empty()); |
| auto assoc_inds = device.GetServiceMsgs<wlan_mlme::AssociateIndication>( |
| fuchsia_wlan_mlme_MLMEAssociateIndOrdinal); |
| ASSERT_EQ(assoc_inds.size(), 1ULL); |
| ctx.AssertAssocInd(std::move(assoc_inds[0])); |
| |
| // Expect association context has been cleared. |
| wlan_assoc_ctx_t expected_ctx = {}; |
| const wlan_assoc_ctx_t* actual_ctx = device.GetStationAssocContext(); |
| EXPECT_EQ(std::memcmp(actual_ctx, &expected_ctx, sizeof(expected_ctx)), 0); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_EmptySsid) { |
| ctx.StartAp(false); |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame without an SSID |
| auto ssid = Span<uint8_t>(); |
| ctx.SendClientAssocReqFrame(ssid, true); |
| |
| // Verify that no Association.indication msg is sent out |
| ASSERT_TRUE(device.svc_queue.empty()); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Send a valid association request frame |
| ctx.SendClientAssocReqFrame(); |
| |
| // Verify that an Association.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto assoc_inds = device.GetServiceMsgs<wlan_mlme::AssociateIndication>( |
| fuchsia_wlan_mlme_MLMEAssociateIndOrdinal); |
| ASSERT_EQ(assoc_inds.size(), 1ULL); |
| ctx.AssertAssocInd(std::move(assoc_inds[0])); |
| } |
| |
| TEST_F(ApInfraBssTest, Associate_EmptyRsn) { |
| ctx.StartAp(false); |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame |
| ctx.SendClientAssocReqFrame(kSsid, false); |
| |
| // Verify that an Association.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto assoc_inds = device.GetServiceMsgs<wlan_mlme::AssociateIndication>( |
| fuchsia_wlan_mlme_MLMEAssociateIndOrdinal); |
| ASSERT_EQ(assoc_inds.size(), 1ULL); |
| ctx.AssertAssocInd(std::move(assoc_inds[0]), false); |
| } |
| |
| TEST_F(ApInfraBssTest, ReauthenticateWhileAssociated) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send authentication request frame |
| ctx.SendClientAuthReqFrame(); |
| |
| // Verify that an Authentication.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto auth_inds = device.GetServiceMsgs<wlan_mlme::AuthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEAuthenticateIndOrdinal); |
| ASSERT_EQ(auth_inds.size(), 1ULL); |
| ctx.AssertAuthInd(std::move(auth_inds[0])); |
| |
| // Simulate SME sending MLME-AUTHENTICATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg( |
| CreateAuthResponse(ctx.client_addr, wlan_mlme::AuthenticateResultCodes::SUCCESS)); |
| |
| // Verify authentication response frame for the client |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| ctx.AssertAuthFrame(std::move(*device.wlan_queue.begin())); |
| } |
| |
| TEST_F(ApInfraBssTest, ReassociationFlowWhileAssociated) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Reauthenticate |
| ctx.AuthenticateClient(); |
| |
| // Send association request frame |
| ctx.SendClientAssocReqFrame(); |
| |
| // Verify that an Association.indication msg is sent out (to SME) |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto assoc_inds = device.GetServiceMsgs<wlan_mlme::AssociateIndication>( |
| fuchsia_wlan_mlme_MLMEAssociateIndOrdinal); |
| ASSERT_EQ(assoc_inds.size(), 1ULL); |
| ctx.AssertAssocInd(std::move(assoc_inds[0])); |
| |
| // Simulate SME sending MLME-ASSOCIATE.response msg with a success code |
| ctx.ap->HandleMlmeMsg( |
| CreateAssocResponse(ctx.client_addr, wlan_mlme::AssociateResultCodes::SUCCESS, kAid)); |
| |
| // Verify association response frame for the client |
| // WLAN queue should have AssociateResponse and BlockAck request |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(2)); |
| ctx.AssertAssocFrame(std::move(*device.wlan_queue.begin())); |
| |
| device.wlan_queue.clear(); |
| ctx.SendEthFrame(kTestPayload); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| } |
| |
| TEST_F(ApInfraBssTest, DeauthenticateWhileAssociated) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send deauthentication frame |
| ctx.SendClientDeauthFrame(); |
| |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| ctx.AssertDeauthInd(std::move(deauth_inds[0]), wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| |
| // Expect association context has been cleared. |
| wlan_assoc_ctx_t expected_ctx = {}; |
| const wlan_assoc_ctx_t* actual_ctx = device.GetStationAssocContext(); |
| EXPECT_EQ(std::memcmp(actual_ctx, &expected_ctx, sizeof(expected_ctx)), 0); |
| } |
| |
| TEST_F(ApInfraBssTest, Disassociate) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send deauthentication frame |
| ctx.SendClientDisassocFrame(); |
| |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto disassoc_inds = device.GetServiceMsgs<wlan_mlme::DisassociateIndication>( |
| fuchsia_wlan_mlme_MLMEDisassociateIndOrdinal); |
| ASSERT_EQ(disassoc_inds.size(), 1ULL); |
| ctx.AssertDisassocInd(std::move(disassoc_inds[0])); |
| |
| // Expect association context has been cleared. |
| wlan_assoc_ctx_t expected_ctx = {}; |
| const wlan_assoc_ctx_t* actual_ctx = device.GetStationAssocContext(); |
| EXPECT_EQ(std::memcmp(actual_ctx, &expected_ctx, sizeof(expected_ctx)), 0); |
| } |
| |
| TEST_F(ApInfraBssTest, Exchange_Eapol_Frames) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send MLME-EAPOL.request. |
| ctx.ap->HandleMlmeMsg(CreateEapolRequest(common::MacAddr(kBssid1), ctx.client_addr)); |
| |
| // Verify MLME-EAPOL.confirm message was sent. |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto msgs = |
| device.GetServiceMsgs<wlan_mlme::EapolConfirm>(fuchsia_wlan_mlme_MLMEEapolConfOrdinal); |
| ASSERT_EQ(msgs.size(), 1ULL); |
| EXPECT_EQ(msgs[0].body()->result_code, wlan_mlme::EapolResultCodes::SUCCESS); |
| |
| // Verify EAPOL frame was sent. |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| auto frame = ctx.TypeCheckWlanFrame<DataFrameView<LlcHeader>>(pkt.pkt.get()); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, ctx.client_addr.byte, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.body()->protocol_id, htobe16(kEapolProtocolId)); |
| auto type_checked_frame = frame.SkipHeader().CheckBodyType<EapolHdr>(); |
| ASSERT_TRUE(type_checked_frame); |
| auto llc_eapol_frame = type_checked_frame.CheckLength(); |
| ASSERT_TRUE(llc_eapol_frame); |
| EXPECT_EQ(llc_eapol_frame.body_len(), static_cast<size_t>(5)); |
| EXPECT_RANGES_EQ(llc_eapol_frame.body_data(), kEapolPdu); |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_HT, WLAN_TX_INFO_FLAGS_FAVOR_RELIABILITY); |
| } |
| |
| TEST_F(ApInfraBssTest, SendFrameAfterAssociation) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Have BSS process Eth frame. |
| ctx.SendEthFrame(kTestPayload); |
| |
| // Verify a data WLAN frame was sent. |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload); |
| } |
| |
| TEST_F(ApInfraBssTest, UnprotectedApReceiveFramesAfterAssociation) { |
| ctx.StartAp(false); |
| |
| // Simulate unauthenticated client sending data frames, which should be ignored |
| ctx.SendDataFrame(kTestPayload); |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| ctx.AuthenticateClient(); |
| ctx.SendDataFrame(kTestPayload); |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| ctx.AssociateClient(kAid); |
| ctx.SendDataFrame(kTestPayload); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Verify ethernet frame is sent out and is correct |
| auto eth_frames = device.GetEthPackets(); |
| ASSERT_EQ(eth_frames.size(), static_cast<size_t>(1)); |
| ctx.AssertEthFrame(std::move(*eth_frames.begin()), kTestPayload); |
| } |
| |
| TEST_F(ApInfraBssTest, MlmeDeauthReqWhileAssociated) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send MLME-DEAUTHENTICATE.request |
| const auto reason_code = wlan_mlme::ReasonCode::FOURWAY_HANDSHAKE_TIMEOUT; |
| ctx.ap->HandleMlmeMsg(CreateDeauthRequest(ctx.client_addr, reason_code)); |
| |
| // Verify MLME-DEAUTHENTICATE.confirm message was sent |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto msgs = device.GetServiceMsgs<wlan_mlme::DeauthenticateConfirm>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateConfOrdinal); |
| ASSERT_EQ(msgs.size(), 1ULL); |
| |
| // Verify deauthenticate frame was sent |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| auto frame = ctx.TypeCheckWlanFrame<MgmtFrameView<Deauthentication>>(pkt.pkt.get()); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr1.byte, kClientAddress, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr2.byte, kBssid1, 6), 0); |
| EXPECT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| EXPECT_EQ(frame.body()->reason_code, static_cast<uint16_t>(reason_code)); |
| AssertSendRate(std::move(pkt), CBW20, WLAN_PHY_OFDM, 0); |
| } |
| |
| TEST_F(ApInfraBssTest, SetKeys) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send MLME-SETKEYS.request |
| auto key_data = std::vector(std::cbegin(kKeyData), std::cend(kKeyData)); |
| ctx.ap->HandleMlmeMsg( |
| CreateSetKeysRequest(ctx.client_addr, key_data, wlan_mlme::KeyType::PAIRWISE)); |
| |
| ASSERT_EQ(device.GetKeys().size(), static_cast<size_t>(1)); |
| auto key_config = std::move(device.GetKeys()[0]); |
| EXPECT_EQ(std::memcmp(key_config.key, kKeyData, sizeof(kKeyData)), 0); |
| EXPECT_EQ(key_config.key_idx, 1); |
| EXPECT_EQ(key_config.key_type, WLAN_KEY_TYPE_PAIRWISE); |
| EXPECT_EQ(std::memcmp(key_config.peer_addr, ctx.client_addr.byte, sizeof(ctx.client_addr)), 0); |
| EXPECT_EQ(std::memcmp(key_config.cipher_oui, kCipherOui, sizeof(kCipherOui)), 0); |
| EXPECT_EQ(key_config.cipher_type, kCipherSuiteType); |
| } |
| |
| TEST_F(ApInfraBssTest, SetKeys_IgnoredForUnprotectedAp) { |
| ctx.StartAp(false); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Send MLME-SETKEYS.request |
| auto key_data = std::vector(std::cbegin(kKeyData), std::cend(kKeyData)); |
| ctx.ap->HandleMlmeMsg( |
| CreateSetKeysRequest(ctx.client_addr, key_data, wlan_mlme::KeyType::PAIRWISE)); |
| |
| EXPECT_TRUE(device.GetKeys().empty()); |
| } |
| |
| TEST_F(ApInfraBssTest, PowerSaving_IgnoredBeforeControlledPortOpens) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Simulate client sending null data frame with power saving. |
| auto pwr_mgmt = true; |
| ctx.SendNullDataFrame(pwr_mgmt); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(0)); |
| |
| // Two Ethernet frames arrive. WLAN frame is sent out since we ignored previous frame and did |
| // not change client's status to dozing |
| std::vector<uint8_t> payload2 = {'m', 's', 'g', '2'}; |
| ctx.SendEthFrame(kTestPayload); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload); |
| } |
| |
| TEST_F(ApInfraBssTest, PowerSaving_AfterControlledPortOpens) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| ctx.EstablishRsna(); |
| |
| // Simulate client sending null data frame with power saving. |
| auto pwr_mgmt = true; |
| ctx.SendNullDataFrame(pwr_mgmt); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(0)); |
| |
| // Two Ethernet frames arrive. Verify no WLAN frame is sent out yet. |
| std::vector<uint8_t> payload2 = {'m', 's', 'g', '2'}; |
| ctx.SendEthFrame(kTestPayload); |
| ctx.SendEthFrame(payload2); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(0)); |
| |
| // Client notifies that it wakes up. Buffered frames should be sent out now |
| ctx.SendNullDataFrame(!pwr_mgmt); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(2)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload, |
| {.more_data = 1, .protected_frame = 1}); |
| pkt = std::move(*(device.wlan_queue.begin() + 1)); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), payload2, {.protected_frame = 1}); |
| } |
| |
| TEST_F(ApInfraBssTest, PowerSaving_UnprotectedAp) { |
| // For unprotected AP, power saving should work as soon as client is associated |
| ctx.StartAp(false); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Simulate client sending null data frame with power saving. |
| auto pwr_mgmt = true; |
| ctx.SendNullDataFrame(pwr_mgmt); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(0)); |
| |
| // Two Ethernet frames arrive. Verify no WLAN frame is sent out yet. |
| std::vector<uint8_t> payload2 = {'m', 's', 'g', '2'}; |
| ctx.SendEthFrame(kTestPayload); |
| ctx.SendEthFrame(payload2); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(0)); |
| |
| // Client notifies that it wakes up. Buffered frames should be sent out now |
| ctx.SendNullDataFrame(!pwr_mgmt); |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(2)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload, {.more_data = 1}); |
| pkt = std::move(*(device.wlan_queue.begin() + 1)); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), payload2); |
| } |
| |
| TEST_F(ApInfraBssTest, OutboundFramesAreProtectedAfterControlledPortOpens) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| ctx.EstablishRsna(); |
| |
| // Have BSS process Eth frame. |
| ctx.SendEthFrame(kTestPayload); |
| |
| // Verify a data WLAN frame was sent with protected frame flag set |
| EXPECT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ctx.AssertDataFrameSentToClient(std::move(pkt), kTestPayload, {.protected_frame = 1}); |
| } |
| |
| TEST_F(ApInfraBssTest, ReceiveFrames_BeforeControlledPortOpens) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| |
| // Simulate client sending data frame to AP |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ctx.SendDataFrame(kTestPayload); |
| |
| // For protected AP, controlled port is not opened until RSNA is established, so data frame |
| // should be ignored |
| EXPECT_TRUE(device.eth_queue.empty()); |
| |
| auto key_data = std::vector(std::cbegin(kKeyData), std::cend(kKeyData)); |
| ctx.ap->HandleMlmeMsg( |
| CreateSetKeysRequest(ctx.client_addr, key_data, wlan_mlme::KeyType::PAIRWISE)); |
| |
| // Simulate client sending data frame to AP |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ctx.SendDataFrame(kTestPayload); |
| |
| // Setting keys doesn't implicit open the controlled port, hence data frame is still ignored |
| EXPECT_TRUE(device.eth_queue.empty()); |
| } |
| |
| TEST_F(ApInfraBssTest, ReceiveFrames_AfterControlledPortOpens) { |
| ctx.StartAp(); |
| ctx.AuthenticateAndAssociateClient(kAid); |
| ctx.EstablishRsna(); |
| |
| // Simulate client sending data frame to AP |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ctx.SendDataFrame(kTestPayload); |
| |
| // Verify ethernet frame is sent out and is correct |
| auto eth_frames = device.GetEthPackets(); |
| ASSERT_EQ(eth_frames.size(), static_cast<size_t>(1)); |
| ctx.AssertEthFrame(std::move(*eth_frames.begin()), kTestPayload); |
| } |
| |
| } // namespace |
| } // namespace wlan |