| // Copyright 2017 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 "test_data.h" |
| #include "test_utils.h" |
| |
| #include <wlan/common/buffer_writer.h> |
| #include <wlan/common/write_element.h> |
| #include <wlan/mlme/client/station.h> |
| #include <wlan/mlme/debug.h> |
| #include <wlan/mlme/mac_frame.h> |
| #include <wlan/mlme/wlan.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| namespace wlan { |
| namespace { |
| |
| constexpr size_t k4BytePaddingLen = 4; |
| |
| struct TestHdr1 { |
| uint8_t a; |
| uint16_t b; |
| uint8_t c; |
| uint8_t d; |
| |
| constexpr size_t len() const { return sizeof(*this); } |
| } __PACKED; |
| |
| // Dynamic length based on value of field `has_padding`. |
| struct TestHdr2 { |
| bool has_padding = false; |
| uint8_t b; |
| uint8_t c; |
| |
| size_t len() const { return sizeof(*this) + (has_padding ? k4BytePaddingLen : 0); } |
| } __PACKED; |
| |
| struct TestHdr3 { |
| uint16_t a; |
| uint16_t b; |
| |
| constexpr size_t len() const { return sizeof(*this); } |
| } __PACKED; |
| |
| struct FixedSizedPayload { |
| uint8_t data[10]; |
| |
| constexpr size_t len() const { return sizeof(*this); } |
| }; |
| |
| // Frame which holds 3 headers and some optional padding and payload. |
| template <size_t padding_len, size_t payload_len> struct TripleHdrFrame { |
| TestHdr1 hdr1; |
| TestHdr2 hdr2; |
| uint8_t padding[padding_len]; |
| TestHdr3 hdr3; |
| uint8_t payload[payload_len]; |
| |
| static constexpr size_t second_frame_len() { |
| return sizeof(TestHdr2) + padding_len + third_frame_len(); |
| } |
| static constexpr size_t second_frame_body_len() { return third_frame_len(); } |
| static constexpr size_t third_frame_len() { return sizeof(TestHdr3) + payload_len; } |
| static constexpr size_t third_frame_body_len() { return payload_len; } |
| static constexpr size_t len() { return sizeof(TripleHdrFrame); } |
| static constexpr size_t body_len() { return second_frame_len(); } |
| |
| } __PACKED; |
| |
| static fbl::unique_ptr<Packet> GetPacket(size_t len) { |
| auto buffer = GetBuffer(len); |
| memset(buffer->data(), 0, len); |
| return fbl::make_unique<Packet>(std::move(buffer), len); |
| } |
| |
| using DefaultTripleHdrFrame = TripleHdrFrame<0, 10>; |
| using PaddedTripleHdrFrame = TripleHdrFrame<4, 10>; |
| |
| TEST(Frame, General) { |
| // Construct initial frame |
| auto pkt = GetPacket(DefaultTripleHdrFrame::len()); |
| auto test_frame = pkt->mut_field<DefaultTripleHdrFrame>(0); |
| test_frame->hdr1.a = 42; |
| test_frame->hdr2.b = 24; |
| |
| // Verify frame's accessors and length. |
| Frame<TestHdr1> frame{std::move(pkt)}; |
| ASSERT_FALSE(frame.IsEmpty()); |
| ASSERT_EQ(frame.len(), DefaultTripleHdrFrame::len()); |
| ASSERT_EQ(frame.hdr()->a, 42); |
| ASSERT_EQ(frame.body_len(), DefaultTripleHdrFrame::body_len()); |
| ASSERT_EQ(frame.body_data()[1], 24); |
| } |
| |
| TEST(Frame, General_Const_Frame) { |
| // Construct initial frame. |
| auto pkt = GetPacket(DefaultTripleHdrFrame::len()); |
| auto test_frame = pkt->mut_field<DefaultTripleHdrFrame>(0); |
| test_frame->hdr1.a = 42; |
| test_frame->hdr2.b = 24; |
| |
| // Verify a constant frame's accessors and length. Constant accessors differ from regular ones. |
| const Frame<TestHdr1> frame{std::move(pkt)}; |
| ASSERT_FALSE(frame.IsEmpty()); |
| ASSERT_EQ(frame.len(), DefaultTripleHdrFrame::len()); |
| ASSERT_EQ(frame.hdr()->a, 42); |
| ASSERT_EQ(frame.body_len(), DefaultTripleHdrFrame::body_len()); |
| ASSERT_EQ(frame.body_data()[1], 24); |
| } |
| |
| TEST(Frame, Take) { |
| // Construct initial frame. |
| auto pkt = GetPacket(DefaultTripleHdrFrame::len()); |
| auto test_frame = pkt->mut_field<DefaultTripleHdrFrame>(0); |
| test_frame->hdr1.a = 42; |
| test_frame->hdr2.b = 24; |
| |
| // Derive frame with unknown body... |
| Frame<TestHdr1> frame(std::move(pkt)); |
| // ... and take the frame's underlying Packet to construct a new, specialized frame. |
| Frame<TestHdr1, TestHdr2> specialized_frame(frame.Take()); |
| // Verify the first frame is considered "taken" and that the new specialized one is valid. |
| ASSERT_TRUE(frame.IsEmpty()); |
| ASSERT_FALSE(specialized_frame.IsEmpty()); |
| ASSERT_EQ(specialized_frame.len(), DefaultTripleHdrFrame::len()); |
| ASSERT_EQ(specialized_frame.hdr()->a, 42); |
| ASSERT_EQ(specialized_frame.body_len(), DefaultTripleHdrFrame::body_len()); |
| ASSERT_EQ(specialized_frame.body()->b, 24); |
| } |
| |
| TEST(Frame, ExactlySizedBuffer_HdrOnly) { |
| // Construct initial frame which has just enough space to hold a header. |
| size_t pkt_len = sizeof(TestHdr1); |
| auto pkt = GetPacket(pkt_len); |
| |
| Frame<TestHdr1> frame(std::move(pkt)); |
| ASSERT_EQ(frame.len(), sizeof(TestHdr1)); |
| ASSERT_EQ(frame.body_len(), static_cast<size_t>(0)); |
| } |
| |
| TEST(Frame, ExactlySizedBuffer_Frame) { |
| // Construct initial frame which has just enough space to hold a header and a body. |
| size_t pkt_len = sizeof(TestHdr1) + sizeof(FixedSizedPayload); |
| auto pkt = GetPacket(pkt_len); |
| |
| Frame<TestHdr1, FixedSizedPayload> frame(std::move(pkt)); |
| ASSERT_EQ(frame.len(), sizeof(TestHdr1) + sizeof(FixedSizedPayload)); |
| ASSERT_EQ(frame.body_len(), sizeof(FixedSizedPayload)); |
| } |
| |
| TEST(Frame, TooShortBuffer_NoHdr) { |
| // Construct initial frame which has not space to hold a header. |
| size_t pkt_len = sizeof(TestHdr1) - 1; |
| auto pkt = GetPacket(pkt_len); |
| } |
| |
| TEST(Frame, RxInfo_MacFrame) { |
| // Construct a large Packet which holds wlan_rx_info_t |
| auto pkt = GetPacket(128); |
| wlan_rx_info_t rx_info{.data_rate = 1337}; |
| pkt->CopyCtrlFrom(rx_info); |
| |
| // Only MAC frames can hold rx_info; |
| MgmtFrame<> mgmt_frame(std::move(pkt)); |
| ASSERT_TRUE(mgmt_frame.View().has_rx_info()); |
| ASSERT_EQ(memcmp(mgmt_frame.View().rx_info(), &rx_info, sizeof(wlan_rx_info_t)), 0); |
| |
| CtrlFrame<PsPollFrame> ctrl_frame(mgmt_frame.Take()); |
| ASSERT_TRUE(ctrl_frame.View().has_rx_info()); |
| ASSERT_EQ(memcmp(ctrl_frame.View().rx_info(), &rx_info, sizeof(wlan_rx_info_t)), 0); |
| |
| MgmtFrame<> data_frame(ctrl_frame.Take()); |
| ASSERT_TRUE(data_frame.View().has_rx_info()); |
| ASSERT_EQ(memcmp(data_frame.View().rx_info(), &rx_info, sizeof(wlan_rx_info_t)), 0); |
| } |
| |
| TEST(Frame, RxInfo_OtherFrame) { |
| // Construct a large Packet which holds wlan_rx_info_t |
| auto pkt = GetPacket(128); |
| wlan_rx_info_t rx_info; |
| pkt->CopyCtrlFrom(rx_info); |
| |
| // Only MAC frames can hold rx_info. Test some others. |
| Frame<TestHdr1> frame1(std::move(pkt)); |
| ASSERT_FALSE(frame1.View().has_rx_info()); |
| Frame<TestHdr1> frame2(frame1.Take()); |
| ASSERT_FALSE(frame2.View().has_rx_info()); |
| Frame<Beacon> frame3(frame2.Take()); |
| ASSERT_FALSE(frame3.View().has_rx_info()); |
| Frame<FrameControl> frame4(frame3.Take()); |
| ASSERT_FALSE(frame4.View().has_rx_info()); |
| Frame<uint8_t> frame5(frame4.Take()); |
| ASSERT_FALSE(frame5.View().has_rx_info()); |
| } |
| |
| TEST(Frame, RxInfo_PaddingAlignedBody) { |
| // Construct frame which holds wlan_rx_info_t and uses additional padding. |
| auto pkt = GetPacket(128); |
| wlan_rx_info_t rx_info{.rx_flags = WLAN_RX_INFO_FLAGS_FRAME_BODY_PADDING_4}; |
| pkt->CopyCtrlFrom(rx_info); |
| auto hdr = pkt->mut_field<DataFrameHeader>(0); |
| // Adjust header to hold 4 addresses which changes the header's length to 30 bytes instead |
| // of 24. This will then cause additional padding for 4-byte alignment. |
| hdr->fc.set_to_ds(1); |
| hdr->fc.set_from_ds(1); |
| ASSERT_EQ(hdr->len(), static_cast<size_t>(30)); |
| // Body should follow after an additional 2 byte padding. |
| auto data = pkt->mut_field<uint8_t>(hdr->len() + 2); |
| data[0] = 42; |
| |
| DataFrame<> data_frame(std::move(pkt)); |
| ASSERT_TRUE(data_frame.View().has_rx_info()); |
| ASSERT_EQ(memcmp(data_frame.View().rx_info(), &rx_info, sizeof(wlan_rx_info_t)), 0); |
| ASSERT_EQ(data_frame.body_data()[0], 42); |
| } |
| |
| TEST(Frame, RxInfo_NoPaddingAlignedBody) { |
| // Construct frame which holds wlan_rx_info_t and uses additional padding. |
| auto pkt = GetPacket(128); |
| wlan_rx_info_t rx_info{.rx_flags = 0}; |
| pkt->CopyCtrlFrom(rx_info); |
| auto hdr = pkt->mut_field<DataFrameHeader>(0); |
| // Adjust header to hold 4 addresses which changes the header's length to 30 bytes instead |
| // of 24. Because rx_info's padding bit is not flipped, the body should not be 4-byte aligned |
| // and thus directly follow the header. |
| hdr->fc.set_to_ds(1); |
| hdr->fc.set_from_ds(1); |
| ASSERT_EQ(hdr->len(), static_cast<size_t>(30)); |
| // Body should follow after an additional 2 byte padding. |
| auto data = pkt->mut_field<uint8_t>(hdr->len()); |
| data[0] = 42; |
| |
| DataFrame<> data_frame(std::move(pkt)); |
| ASSERT_TRUE(data_frame.View().has_rx_info()); |
| ASSERT_EQ(memcmp(data_frame.View().rx_info(), &rx_info, sizeof(wlan_rx_info_t)), 0); |
| ASSERT_EQ(data_frame.body_data()[0], 42); |
| } |
| |
| TEST(Frame, ConstructEmptyFrame) { |
| Frame<TestHdr1> frame; |
| ASSERT_TRUE(frame.IsEmpty()); |
| } |
| |
| TEST(Frame, AdvanceThroughAmsduFrame) { |
| constexpr size_t kPadding = 2; |
| |
| auto frame_data = test_data::kAmsduDataFrame; |
| auto pkt = GetPacket(frame_data.size()); |
| pkt->CopyFrom(frame_data.data(), frame_data.size(), 0); |
| |
| auto opt_data_frame = DataFrameView<>::CheckType(pkt.get()); |
| ASSERT_TRUE(opt_data_frame); |
| auto data_frame = opt_data_frame.CheckLength(); |
| ASSERT_TRUE(data_frame); |
| |
| auto opt_data_amsdu_frame = data_frame.CheckBodyType<AmsduSubframeHeader>(); |
| ASSERT_TRUE(opt_data_amsdu_frame); |
| auto data_amsdu_frame = opt_data_amsdu_frame.CheckLength(); |
| ASSERT_TRUE(data_amsdu_frame); |
| |
| auto amsdu_subframe1 = data_amsdu_frame.SkipHeader(); |
| ASSERT_TRUE(amsdu_subframe1); |
| auto opt_amsdu_llc_subframe1 = amsdu_subframe1.CheckBodyType<LlcHeader>(); |
| ASSERT_TRUE(opt_amsdu_llc_subframe1); |
| auto amsdu_llc_subframe1 = opt_amsdu_llc_subframe1.CheckLength(); |
| ASSERT_TRUE(amsdu_llc_subframe1); |
| |
| size_t msdu_len = amsdu_llc_subframe1.hdr()->msdu_len(); |
| ASSERT_EQ(msdu_len, static_cast<uint16_t>(116)); |
| auto llc_frame = amsdu_llc_subframe1.SkipHeader(); |
| ASSERT_TRUE(llc_frame); |
| |
| auto opt_amsdu_llc_subframe2 = |
| llc_frame.AdvanceBy(msdu_len + kPadding).As<AmsduSubframeHeader>(); |
| ASSERT_TRUE(opt_amsdu_llc_subframe2); |
| auto amsdu_llc_subframe2 = opt_amsdu_llc_subframe2.CheckLength(); |
| ASSERT_TRUE(amsdu_llc_subframe2); |
| |
| msdu_len = amsdu_llc_subframe2.hdr()->msdu_len(); |
| ASSERT_EQ(msdu_len, static_cast<uint16_t>(102)); |
| } |
| |
| TEST(Frame, AdvanceThroughEmptyFrame) { |
| MgmtFrameView<> empty_frame; |
| ASSERT_FALSE(empty_frame); |
| ASSERT_FALSE(empty_frame.SkipHeader()); |
| ASSERT_FALSE(empty_frame.CheckBodyType<Beacon>()); |
| ASSERT_FALSE(empty_frame.AdvanceBy(5)); |
| ASSERT_FALSE(empty_frame.As<DataFrameHeader>()); |
| } |
| |
| TEST(Frame, AdvanceOutOfBounds) { |
| auto pkt = GetPacket(20); |
| DataFrameView<> frame(pkt.get()); |
| ASSERT_TRUE(frame); |
| |
| ASSERT_TRUE(frame.AdvanceBy(20)); |
| ASSERT_FALSE(frame.AdvanceBy(21)); |
| } |
| |
| TEST(Frame, AdvanceThroughEapolFrame) { |
| // The test frame uses padding after it's data header. |
| // Setup a Packet which respects this. |
| auto frame_data = test_data::kDataLlcEapolFrame; |
| auto pkt = GetPacket(frame_data.size()); |
| pkt->CopyFrom(frame_data.data(), frame_data.size(), 0); |
| wlan_rx_info_t rx_info{.rx_flags = WLAN_RX_INFO_FLAGS_FRAME_BODY_PADDING_4}; |
| pkt->CopyCtrlFrom(rx_info); |
| |
| auto opt_data_frame = DataFrameView<>::CheckType(pkt.get()); |
| ASSERT_TRUE(opt_data_frame); |
| auto data_frame = opt_data_frame.CheckLength(); |
| ASSERT_TRUE(data_frame); |
| |
| auto opt_data_llc_frame = data_frame.CheckBodyType<LlcHeader>(); |
| ASSERT_TRUE(opt_data_llc_frame); |
| auto data_llc_frame = opt_data_llc_frame.CheckLength(); |
| ASSERT_TRUE(data_llc_frame); |
| ASSERT_EQ(be16toh(data_llc_frame.body()->protocol_id), kEapolProtocolId); |
| |
| auto llc_frame = data_llc_frame.SkipHeader(); |
| ASSERT_TRUE(llc_frame); |
| ASSERT_EQ(be16toh(llc_frame.hdr()->protocol_id), kEapolProtocolId); |
| auto opt_llc_eapol_frame = llc_frame.CheckBodyType<EapolHdr>(); |
| ASSERT_TRUE(opt_llc_eapol_frame); |
| auto llc_eapol_frame = opt_llc_eapol_frame.CheckLength(); |
| ASSERT_TRUE(llc_eapol_frame); |
| |
| auto eapol_frame = llc_eapol_frame.SkipHeader(); |
| ASSERT_TRUE(eapol_frame); |
| ASSERT_EQ(eapol_frame.hdr()->packet_type, 0x03); |
| } |
| |
| TEST(Frame, DeaggregateAmsdu) { |
| // Construct AMSDU data frame. |
| auto frame_data = test_data::kAmsduDataFrame; |
| auto pkt = GetPacket(frame_data.size()); |
| pkt->CopyFrom(frame_data.data(), frame_data.size(), 0); |
| |
| auto data_amsdu_frame = DataFrameView<AmsduSubframeHeader>::CheckType(pkt.get()).CheckLength(); |
| ASSERT_TRUE(data_amsdu_frame); |
| |
| // Extract all LLC MSDUs from AMSDU frame. |
| std::vector<std::pair<FrameView<LlcHeader>, size_t>> llc_frames; |
| DeaggregateAmsdu(data_amsdu_frame, [&](FrameView<LlcHeader> llc_frame, size_t payload_len) { |
| std::pair<FrameView<LlcHeader>, size_t> e(llc_frame, payload_len); |
| llc_frames.push_back(e); |
| }); |
| |
| // Verify LLC MSDUs. |
| ASSERT_EQ(llc_frames.size(), static_cast<size_t>(2)); |
| ASSERT_EQ(llc_frames[0].first.body_data()[3], static_cast<uint8_t>(0x6c)); |
| ASSERT_EQ(llc_frames[0].second, static_cast<size_t>(108)); |
| ASSERT_EQ(llc_frames[1].first.body_data()[3], static_cast<uint8_t>(0x5e)); |
| ASSERT_EQ(llc_frames[1].second, static_cast<size_t>(94)); |
| } |
| |
| TEST(Frame, EmptyBodyData) { |
| size_t pkt_len = sizeof(TestHdr1); |
| auto pkt = GetPacket(pkt_len); |
| Frame<TestHdr1> frame(std::move(pkt)); |
| ASSERT_TRUE(frame.body_data().empty()); |
| } |
| |
| TEST(Frame, PopulatedBodyData) { |
| size_t pkt_len = sizeof(TestHdr1) + 10; |
| auto pkt = GetPacket(pkt_len); |
| Frame<TestHdr1> frame(std::move(pkt)); |
| ASSERT_EQ(frame.body_data().size_bytes(), 10lu); |
| } |
| |
| TEST(Frame, DdkConversion) { |
| // DDK uint32_t to class CapabilityInfo |
| uint32_t ddk_caps = 0; |
| auto ieee_caps = CapabilityInfo::FromDdk(ddk_caps); |
| EXPECT_EQ(0, ieee_caps.val()); |
| |
| ddk_caps |= WLAN_CAP_SHORT_PREAMBLE; |
| ieee_caps = CapabilityInfo::FromDdk(ddk_caps); |
| EXPECT_EQ(1, ieee_caps.short_preamble()); |
| EXPECT_EQ(0, ieee_caps.spectrum_mgmt()); |
| EXPECT_EQ(0, ieee_caps.short_slot_time()); |
| EXPECT_EQ(0, ieee_caps.radio_msmt()); |
| EXPECT_EQ(0x0020, ieee_caps.val()); |
| |
| ddk_caps = WLAN_CAP_SHORT_PREAMBLE | WLAN_CAP_SHORT_SLOT_TIME; |
| ieee_caps = CapabilityInfo::FromDdk(ddk_caps); |
| EXPECT_EQ(1, ieee_caps.short_preamble()); |
| EXPECT_EQ(0, ieee_caps.spectrum_mgmt()); |
| EXPECT_EQ(1, ieee_caps.short_slot_time()); |
| EXPECT_EQ(0, ieee_caps.radio_msmt()); |
| EXPECT_EQ(0x420, ieee_caps.val()); |
| } |
| |
| TEST(Frame, ParseProbeRequests) { |
| auto frame_data = test_data::kProbeRequestFrame; |
| auto pkt = GetPacket(frame_data.size()); |
| pkt->CopyFrom(frame_data.data(), frame_data.size(), 0); |
| |
| MgmtFrame<ProbeRequest> probe_req(std::move(pkt)); |
| uint8_t expected_ie_chain[] = { |
| 0x00, 0x00, 0x01, 0x08, 0x0c, 0x12, 0x18, 0x24, |
| 0x30, 0x48, 0x60, 0x6c, 0x2d, 0x1a, 0xef, 0x01, |
| 0x13, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x7f, 0x09, 0x04, 0x00, 0x0a, 0x02, 0x01, 0x00, |
| 0x00, 0x40, 0x80, 0xbf, 0x0c, 0xb2, 0x79, 0x91, |
| 0x33, 0xfa, 0xff, 0x0c, 0x03, 0xfa, 0xff, 0x0c, |
| 0x03, 0xdd, 0x07, 0x00, 0x50, 0xf2, 0x08, 0x00, |
| 0x23, 0x00, 0xff, 0x03, 0x02, 0x00, 0x1c |
| }; |
| EXPECT_RANGES_EQ(probe_req.body_data(), expected_ie_chain); |
| } |
| |
| } // namespace |
| } // namespace wlan |