From fe4cd3b3629026e15ef969d1886874735b796760 Mon Sep 17 00:00:00 2001 From: Davide Magrin Date: Wed, 2 Feb 2022 21:00:41 +0100 Subject: [PATCH] wifi: Add TIM Information Element and tests for PS --- src/wifi/CMakeLists.txt | 3 + src/wifi/model/tim.cc | 231 ++++++++++++++++++++++++++ src/wifi/model/tim.h | 161 ++++++++++++++++++ src/wifi/test/power-save-test.cc | 277 +++++++++++++++++++++++++++++++ 4 files changed, 672 insertions(+) create mode 100644 src/wifi/model/tim.cc create mode 100644 src/wifi/model/tim.h create mode 100644 src/wifi/test/power-save-test.cc diff --git a/src/wifi/CMakeLists.txt b/src/wifi/CMakeLists.txt index b4b07e5fb..e35956ce7 100644 --- a/src/wifi/CMakeLists.txt +++ b/src/wifi/CMakeLists.txt @@ -111,6 +111,7 @@ set(source_files model/supported-rates.cc model/table-based-error-rate-model.cc model/threshold-preamble-detection-model.cc + model/tim.cc model/txop.cc model/vht/vht-capabilities.cc model/vht/vht-configuration.cc @@ -265,6 +266,7 @@ set(header_files model/supported-rates.h model/table-based-error-rate-model.h model/threshold-preamble-detection-model.h + model/tim.h model/txop.h model/vht/vht-capabilities.h model/vht/vht-configuration.h @@ -333,6 +335,7 @@ build_lib( test/channel-access-manager-test.cc test/inter-bss-test-suite.cc test/power-rate-adaptation-test.cc + test/power-save-test.cc test/spectrum-wifi-phy-test.cc test/tx-duration-test.cc test/wifi-aggregation-test.cc diff --git a/src/wifi/model/tim.cc b/src/wifi/model/tim.cc new file mode 100644 index 000000000..4533caf50 --- /dev/null +++ b/src/wifi/model/tim.cc @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2022 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Davide Magrin + */ + +#include "tim.h" + +#include +#include + +namespace ns3 +{ + +WifiInformationElementId +Tim::ElementId() const +{ + return IE_TIM; +} + +uint16_t +Tim::GetInformationFieldSize() const +{ + // When the TIM is carried in a non-S1G PPDU, in the event that all bits other than bit 0 in + // the traffic indication virtual bitmap are 0, the Partial Virtual Bitmap field is encoded as + // a single octet equal to 0, the Bitmap Offset subfield is 0, and the Length field is 4. + // (Sec. 9.4.2.5.1 of 802.11-2020) + // The size of the information field is the size of the Partial Virtual Bitmap field, + // plus one octet each for the DTIM Count, DTIM Period, and Bitmap Control fields + uint16_t partialVirtualBitmapSize = + GetLastNonZeroOctetIndex() - GetPartialVirtualBitmapOffset() + 1; + return partialVirtualBitmapSize + 3; +} + +void +Tim::AddAid(uint16_t aid) +{ + NS_ABORT_IF(aid > 2007); + + m_aidValues.insert(aid); +} + +bool +Tim::HasAid(uint16_t aid) const +{ + return m_aidValues.find(aid) != m_aidValues.end(); +} + +std::set +Tim::GetAidSet(uint16_t aid) const +{ + auto start = m_aidValues.upper_bound(aid); + return std::set(start, m_aidValues.cend()); +} + +void +Tim::SerializeInformationField(Buffer::Iterator start) const +{ + start.WriteU8(m_dtimCount); + start.WriteU8(m_dtimPeriod); + + // the Bitmap Control field is optional if the TIM is carried in an S1G PPDU, while + // it is always present when the TIM is carried in a non-S1G PPDU + start.WriteU8(GetBitmapControl()); + auto partialVirtualBitmap = GetPartialVirtualBitmap(); + for (auto byte : partialVirtualBitmap) + { + start.WriteU8(byte); + } +} + +uint16_t +Tim::DeserializeInformationField(Buffer::Iterator start, uint16_t length) +{ + NS_ABORT_MSG_IF(length < 2, "Invalid length: " << length); + + m_dtimCount = start.ReadU8(); + m_dtimPeriod = start.ReadU8(); + + if (length == 2) + { + // no Bitmap Control field nor Partial Virtual Bitmap field + return 2; + } + + // Bitmap control field: here we determine the presence of multicast traffic and the offset + auto bitmapControl = start.ReadU8(); + // Least significant bit is the Traffic Indication field + m_hasMulticastPending = bitmapControl & 0x01; + // Other bits are the Bitmap Offset + uint8_t partialVirtualBitmapOffset = bitmapControl & 0xFE; + // Next, deserialize the partial virtual bitmap + uint16_t octetIndex; + // length is the length of the information fields, so we need to + // subtract 3 to get the length of the Partial Virtual Bitmap + for (octetIndex = partialVirtualBitmapOffset; + octetIndex < static_cast(partialVirtualBitmapOffset + length - 3); + ++octetIndex) + { + if (auto octet = start.ReadU8(); octet > 0) + { + // Look for bits set to 1 + for (uint8_t position = 0; position < 8; position++) + { + if ((octet >> position) & 0x1) + { + m_aidValues.insert(GetAidFromOctetIndexAndBitPosition(octetIndex, position)); + } + } + } + } + return 3 + octetIndex - partialVirtualBitmapOffset; +} + +uint8_t +Tim::GetAidOctetIndex(uint16_t aid) const +{ + // bit number N (0 <= N <= 2007) in the bitmap corresponds to bit number (N mod 8) in octet + // number |_N / 8_| where the low order bit of each octet is bit number 0, and the high order + // bit is bit number 7 (Sec. 9.4.2.5.1 of 802.11-2020) + return (aid >> 3) & 0xff; +} + +uint8_t +Tim::GetAidBit(uint16_t aid) const +{ + // bit number N (0 <= N <= 2007) in the bitmap corresponds to bit number (N mod 8) in octet + // number |_N / 8_| where the low order bit of each octet is bit number 0, and the high order + // bit is bit number 7 (Sec. 9.4.2.5.1 of 802.11-2020) + return 0x01 << (aid & 0x07); +} + +uint16_t +Tim::GetAidFromOctetIndexAndBitPosition(uint16_t octet, uint8_t position) const +{ + return (octet << 3) + position; +} + +uint8_t +Tim::GetPartialVirtualBitmapOffset() const +{ + if (m_aidValues.empty()) + { + return 0; + } + // N1 is the largest even number such that bits numbered 1 to (N1 * 8) – 1 in the traffic + // indication virtual bitmap are all 0 (Sec. 9.4.2.5.1 of 802.11-2020). + // Examples: + // first bit set = 53, which belongs to octet 53 / 8 = 6 -> N1 = 6 (all bits 1 - 47 are zero) + // first bit set = 61, which belongs to octet 61 / 8 = 7 -> N1 = 6 (all bits 1 - 47 are zero) + return GetAidOctetIndex(*m_aidValues.cbegin()) & 0xFE; +} + +uint8_t +Tim::GetLastNonZeroOctetIndex() const +{ + if (m_aidValues.empty()) + { + return 0; + } + // N2 is the smallest number such that bits numbered (N2 + 1) * 8 to 2007 in the traffic + // indication virtual bitmap are all 0 (Sec. 9.4.2.5.1 of 802.11-2020). + // Examples: + // last bit set = 53, which belongs to octet 53 / 8 = 6 -> N2 = 6 (all bits 56 - 2007 are zero) + // last bit set = 61, which belongs to octet 61 / 8 = 7 -> N2 = 7 (all bits 64 - 2007 are zero) + return GetAidOctetIndex(*m_aidValues.rbegin()); +} + +uint8_t +Tim::GetBitmapControl() const +{ + // Note that setting the bitmapControl directly as the offset can be done because the least + // significant bit of the output of GetPartialVirtualBitmapOffset will always be zero, so we + // are already putting the relevant information in the appropriate part of the byte. + auto bitmapControl = GetPartialVirtualBitmapOffset(); + + // Set the multicast indication bit, if this is a DTIM + if ((m_dtimCount == 0) && m_hasMulticastPending) + { + bitmapControl |= 0x01; + } + + return bitmapControl; +} + +std::vector +Tim::GetPartialVirtualBitmap() const +{ + auto offset = GetPartialVirtualBitmapOffset(); // N1 + + // the Partial Virtual Bitmap field consists of octets numbered N1 to N2 of the traffic + // indication virtual bitmap (Sec. 9.4.2.5.1 of 802.11-2020) + std::vector partialVirtualBitmap(GetLastNonZeroOctetIndex() - offset + 1, 0); + + for (auto aid : m_aidValues) + { + partialVirtualBitmap.at(GetAidOctetIndex(aid) - offset) |= GetAidBit(aid); + } + + return partialVirtualBitmap; +} + +void +Tim::Print(std::ostream& os) const +{ + os << "DTIM Count: " << +m_dtimCount << ", " + << "DTIM Period: " << +m_dtimPeriod << ", " + << "Has Multicast Pending: " << m_hasMulticastPending << ", AID values:"; + for (uint16_t aid = 0; aid < 2008; ++aid) + { + if (HasAid(aid)) + { + os << aid << " "; + } + } +} + +} // namespace ns3 diff --git a/src/wifi/model/tim.h b/src/wifi/model/tim.h new file mode 100644 index 000000000..acbb2d04c --- /dev/null +++ b/src/wifi/model/tim.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2022 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Davide Magrin + */ + +#ifndef TIM_H +#define TIM_H + +#include "wifi-information-element.h" + +#include + +namespace ns3 +{ + +/** + * \brief The Traffic Indication Map Information Element + * \ingroup wifi + * + * The 802.11 Traffic Indication Map (see section 9.4.2.5 of 802.11-2020) + * + * Note: The current implementation does not support S1G operation, or + * multiple BSSID. + */ +class Tim : public WifiInformationElement +{ + public: + WifiInformationElementId ElementId() const override; + void Print(std::ostream& os) const override; + + /** + * Add the provided AID value to the list contained in the Virtual Bitmap + * + * \param aid the AID value to add to this TIM's Virtual Bitmap + */ + void AddAid(uint16_t aid); + + /** + * Add the AID values in the provided iterator range to the list contained + * in the Virtual Bitmap + * + * \tparam Iterator Type of iterator + * \param begin Starting position of the iterator range + * \param end Ending position of the iterator range + */ + template + void AddAid(Iterator begin, Iterator end); + + /** + * Check whether the bit corresponding to the provided AID is set in the + * Virtual Bitmap included in this TIM + * + * \param aid The AID value to look for + * \return True if the AID value is found in the Virtual Bitmap, false otherwise + */ + bool HasAid(uint16_t aid) const; + + /** + * Return the AID values, greater than the given AID value, whose corresponding bits are set + * in the virtual bitmap. + * + * \param aid the given AID value + * \return the AID values, greater than the given AID value, whose corresponding bits are set + * in the virtual bitmap + */ + std::set GetAidSet(uint16_t aid = 0) const; + + /** + * Get the Partial Virtual Bitmap offset, i.e., the number (denoted as N1 by the specs) of + * the first octet included in the Partial Virtual Bitmap. Note that the Bitmap Offset + * subfield contains the number N1/2. + * + * \return the Partial Virtual Bitmap offset + */ + uint8_t GetPartialVirtualBitmapOffset() const; + + /** + * \return the last non-zero octet in the virtual bitmap (denoted as N2 by the specs) + */ + uint8_t GetLastNonZeroOctetIndex() const; + + uint8_t m_dtimCount{0}; //!< The DTIM Count field + uint8_t m_dtimPeriod{0}; //!< The DTIM Period field + bool m_hasMulticastPending{false}; //!< Whether there is Multicast / Broadcast data + + private: + uint16_t GetInformationFieldSize() const override; + void SerializeInformationField(Buffer::Iterator start) const override; + uint16_t DeserializeInformationField(Buffer::Iterator start, uint16_t length) override; + + /** + * Obtain the index of the octet where the provided AID value should be + * set in the Virtual Bitmap + * + * \param aid the provided AID value + * \return the index of the octet where the provided AID value should be + * set in the Virtual Bitmap + */ + uint8_t GetAidOctetIndex(uint16_t aid) const; + + /** + * Obtain an octet with a set bit, corresponding to the provided AID value + * + * \param aid the provided AID value + * \return an octet with a set bit, corresponding to the provided AID value + */ + uint8_t GetAidBit(uint16_t aid) const; + + /** + * Obtain the AID value represented by a certain octet index and bit + * position inside the Virtual Bitmap + * + * \param octet the octet index in the Virtual Bitmap + * \param position the bit position in the octet of the Virtual Bitmap + * \return the corresponding AID value + */ + uint16_t GetAidFromOctetIndexAndBitPosition(uint16_t octet, uint8_t position) const; + + /** + * The Bitmap Control field is optional if the TIM is carried in an S1G PPDU, while + * it is always present when the TIM is carried in a non-S1G PPDU. + * + * \return the value of the Bitmap Control field + */ + uint8_t GetBitmapControl() const; + + /** + * \return a vector containing the Partial Virtual Bitmap octets + */ + std::vector GetPartialVirtualBitmap() const; + + std::set m_aidValues; //!< List of AID values included in this TIM +}; + +template +void +Tim::AddAid(Iterator begin, Iterator end) +{ + for (auto& it = begin; it != end; it++) + { + AddAid(*it); + } +} + +} // namespace ns3 + +#endif /* TIM_H */ diff --git a/src/wifi/test/power-save-test.cc b/src/wifi/test/power-save-test.cc new file mode 100644 index 000000000..71597399b --- /dev/null +++ b/src/wifi/test/power-save-test.cc @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2022 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Davide Magrin + */ + +#include "ns3/assert.h" +#include "ns3/header-serialization-test.h" +#include "ns3/log.h" +#include "ns3/test.h" +#include "ns3/tim.h" + +#include +#include +#include +#include +#include + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE("PowerSaveTest"); + +/** + * \ingroup wifi-test + * \ingroup tests + * + * \brief Test TIM Information element serialization and deserialization + */ +class TimInformationElementTest : public HeaderSerializationTestCase +{ + public: + /** + * \brief Constructor + */ + TimInformationElementTest(); + + void DoRun() override; + /** + * Reset the passed TIM to have the provided parameters. + * + * \param tim the TIM element to set + * \param dtimCount the DTIM count value + * \param dtimPeriod the DTIM period value + * \param multicastPending whether group addressed frames are queued + * \param aidValues the AID values to set + */ + void SetTim(Tim& tim, + uint8_t dtimCount, + uint8_t dtimPeriod, + bool multicastPending, + const std::list& aidValues); + + /** + * Test that the Bitmap Control and the Partial Virtual Bitmap + * fields of the provided TIM match the passed bufferContents. + * + * \param tim the provided TIM + * \param bufferContents the expected content of the buffer + */ + void CheckSerializationAgainstBuffer(Tim& tim, const std::vector& bufferContents); + + /** + * Test that the GetAidSet() method return the expected set of AID values. + * + * \param tim the TIM element + * \param aid the AID value passed to GetAidSet() + * \param expectedSet the expected set of AID values returned by GetAidSet() + */ + void CheckAidSet(const Tim& tim, uint16_t aid, const std::set& expectedSet); +}; + +TimInformationElementTest::TimInformationElementTest() + : HeaderSerializationTestCase("Test for the TIM Information Element implementation") +{ +} + +void +TimInformationElementTest::SetTim(Tim& tim, + uint8_t dtimCount, + uint8_t dtimPeriod, + bool multicastPending, + const std::list& aidValues) +{ + tim = Tim(); + tim.m_dtimCount = dtimCount; + tim.m_dtimPeriod = dtimPeriod; + tim.m_hasMulticastPending = multicastPending; + tim.AddAid(aidValues.begin(), aidValues.end()); +} + +void +TimInformationElementTest::CheckSerializationAgainstBuffer( + Tim& tim, + const std::vector& bufferContents) +{ + // Serialize the TIM + Buffer buffer; + buffer.AddAtStart(tim.GetSerializedSize()); + tim.Serialize(buffer.Begin()); + + // Check the two Buffer instances + Buffer::Iterator bufferIterator = buffer.Begin(); + for (uint32_t j = 0; j < buffer.GetSize(); j++) + { + // We skip the first four bytes, since they contain known information + if (j > 3) + { + NS_TEST_EXPECT_MSG_EQ(bufferIterator.ReadU8(), + bufferContents.at(j - 4), + "Serialization is different than provided known serialization"); + } + else + { + // Advance the serialized buffer, which also contains + // the Element ID, Length, DTIM Count, DTIM Period fields + bufferIterator.ReadU8(); + } + } +} + +void +TimInformationElementTest::CheckAidSet(const Tim& tim, + uint16_t aid, + const std::set& expectedSet) +{ + auto ret = tim.GetAidSet(aid); + + { + std::vector diff; + + // Expected set minus returned set provides expected elements that are not returned + std::set_difference(expectedSet.cbegin(), + expectedSet.cend(), + ret.cbegin(), + ret.cend(), + std::back_inserter(diff)); + + std::stringstream ss; + std::copy(diff.cbegin(), diff.cend(), std::ostream_iterator(ss, " ")); + + NS_TEST_EXPECT_MSG_EQ(diff.size(), + 0, + "Expected elements not returned by GetAidSet(): " << ss.str()); + } + { + std::vector diff; + + // Returned set minus expected set provides returned elements that are not expected + std::set_difference(ret.cbegin(), + ret.cend(), + expectedSet.cbegin(), + expectedSet.cend(), + std::back_inserter(diff)); + + std::stringstream ss; + std::copy(diff.cbegin(), diff.cend(), std::ostream_iterator(ss, " ")); + + NS_TEST_EXPECT_MSG_EQ(diff.size(), + 0, + "Returned elements not expected by GetAidSet(): " << ss.str()); + } +} + +void +TimInformationElementTest::DoRun() +{ + Tim tim; + + // The first three examples from 802.11-2020, Annex L + // + // 1. No group addressed MSDUs, but there is traffic for STAs with AID 2 and AID 7 + SetTim(tim, 0, 3, false, {2, 7}); + TestHeaderSerialization(tim); + CheckSerializationAgainstBuffer(tim, {0b00000000, 0b10000100}); + CheckAidSet(tim, 0, {2, 7}); + CheckAidSet(tim, 1, {2, 7}); + CheckAidSet(tim, 2, {7}); + CheckAidSet(tim, 7, {}); + // + // 2. There are group addressed MSDUs, DTIM count = 0, the nodes + // with AID 2, 7, 22, and 24 have data buffered in the AP + SetTim(tim, 0, 3, true, {2, 7, 22, 24}); + TestHeaderSerialization(tim); + CheckSerializationAgainstBuffer(tim, + { + 0b00000001, + // NOTE The following byte is different from the example + // in the standard. This is because the example sets the + // AID 0 bit in the partial virtual bitmap to 1. Our code + // and the example code provided in the Annex, instead, do + // not set this bit. Relevant Note from 802.11-2020, + // Section 9.4.2.5.1: "The bit numbered 0 in the traffic + // indication virtual bitmap need not be included in the + // Partial Virtual Bitmap field even if that bit is set." + 0b10000100, + 0b00000000, + 0b01000000, + 0b00000001, + }); + CheckAidSet(tim, 0, {2, 7, 22, 24}); + CheckAidSet(tim, 2, {7, 22, 24}); + CheckAidSet(tim, 7, {22, 24}); + CheckAidSet(tim, 22, {24}); + CheckAidSet(tim, 24, {}); + // + // 3. There are group addressed MSDUs, DTIM count = 0, only the node + // with AID 24 has data buffered in the AP + SetTim(tim, 0, 3, true, {24}); + TestHeaderSerialization(tim); + CheckSerializationAgainstBuffer(tim, {0b00000011, 0b00000000, 0b00000001}); + + // Other arbitrary examples just to make sure + // Serialization -> Deserialization -> Serialization works + SetTim(tim, 0, 3, false, {2000}); + TestHeaderSerialization(tim); + SetTim(tim, 1, 3, true, {1, 134}); + TestHeaderSerialization(tim); + SetTim(tim, 1, 3, false, {1, 2}); + TestHeaderSerialization(tim); + + // Edge cases + // + // What if there is group addressed data only? + // + // In this case, we should still have an empty byte in the Partial Virtual Bitmap. + // From 802.11-2020: in the event that all bits other than bit 0 in the traffic indication + // virtual bitmap are 0, the Partial Virtual Bitmap field is encoded as a single octet + // equal to 0, the Bitmap Offset subfield is 0, and the Length field is 4. + SetTim(tim, 0, 3, true, {}); + TestHeaderSerialization(tim); + CheckSerializationAgainstBuffer(tim, {0b00000001, 0b00000000}); + NS_TEST_EXPECT_MSG_EQ(tim.GetSerializedSize() - 2, 4, "Unexpected TIM Length"); + // + // What if there is no group addressed data and no unicast data? + // + // From 802.11-2020: When the TIM is carried in a non-S1G PPDU, in the event that all bits + // other than bit 0 in the traffic indication virtual bitmap are 0, the Partial Virtual Bitmap + // field is encoded as a single octet equal to 0, the Bitmap Offset subfield is 0, and the + // Length field is 4. + SetTim(tim, 0, 3, false, {}); + TestHeaderSerialization(tim); + CheckSerializationAgainstBuffer(tim, {0b00000000, 0b00000000}); + NS_TEST_EXPECT_MSG_EQ(tim.GetSerializedSize() - 2, 4, "Unexpected TIM Length"); +} + +/** + * \ingroup wifi-test + * \ingroup tests + * + * \brief Power Save Test Suite + */ +class PowerSaveTestSuite : public TestSuite +{ + public: + PowerSaveTestSuite(); +}; + +PowerSaveTestSuite::PowerSaveTestSuite() + : TestSuite("wifi-power-save", Type::UNIT) +{ + AddTestCase(new TimInformationElementTest, TestCase::Duration::QUICK); +} + +static PowerSaveTestSuite g_powerSaveTestSuite; ///< the test suite