[bit-set] enhance BitSet class (#13156)

This commit extends the `BitSet` class with several new
methods:
- `CountElements()`
- `IsSubsetOf()` and `IsSupersetOf()`
- `Complement()`
- `UnionWith()`, `IntersectWith()`, and `SubtractWith()`
- `SetMask()`, `AppendTo()`, and `ReadFrom()` message.

This commit also introduces a new `BitSetUtils` non-template base class
for the `BitSet<kNumBits>` template class. This change helps optimize
code by moving the common implementation logic for various bit
manipulation operations out of the template, reducing template
instantiation overhead.
This commit is contained in:
Abtin Keshavarzian
2026-05-27 10:21:44 -07:00
committed by GitHub
parent a18123b349
commit 0693bceb75
6 changed files with 507 additions and 16 deletions
+1
View File
@@ -424,6 +424,7 @@ openthread_core_files = [
"common/as_core_type.hpp",
"common/binary_search.cpp",
"common/binary_search.hpp",
"common/bit_set.cpp",
"common/bit_set.hpp",
"common/bit_utils.cpp",
"common/bit_utils.hpp",
+1
View File
@@ -114,6 +114,7 @@ set(COMMON_SOURCES
coap/coap_secure.cpp
common/appender.cpp
common/binary_search.cpp
common/bit_set.cpp
common/bit_utils.cpp
common/crc.cpp
common/data.cpp
+155
View File
@@ -0,0 +1,155 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements utility function for a bit-set.
*/
#include "bit_set.hpp"
#include "common/code_utils.hpp"
#include "common/message.hpp"
namespace ot {
bool BitSetUtils::IsAllZero(const uint8_t *aMask, uint16_t aSize)
{
bool allZero = true;
for (uint16_t i = 0; i < aSize; i++)
{
VerifyOrExit(aMask[i] == 0, allZero = false);
}
exit:
return allZero;
}
uint16_t BitSetUtils::CountBits(const uint8_t *aMask, uint16_t aSize)
{
uint16_t count = 0;
for (uint16_t i = 0; i < aSize; i++)
{
count += CountBitsInMask(aMask[i]);
}
return count;
}
void BitSetUtils::FlipBits(uint8_t *aTargetMask, uint16_t aSize, uint16_t aNumBits)
{
for (uint16_t i = 0; i < aSize; i++)
{
aTargetMask[i] = ~aTargetMask[i];
}
Tidy(aTargetMask, aSize, aNumBits);
}
bool BitSetUtils::IsSubset(const uint8_t *aMask, const uint8_t *aSuperMask, uint16_t aSize)
{
bool isSubset = true;
for (uint16_t i = 0; i < aSize; i++)
{
VerifyOrExit((aMask[i] & aSuperMask[i]) == aMask[i], isSubset = false);
}
exit:
return isSubset;
}
void BitSetUtils::Union(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize)
{
for (uint16_t i = 0; i < aSize; i++)
{
aTargetMask[i] |= aMask[i];
}
}
void BitSetUtils::Intersect(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize)
{
for (uint16_t i = 0; i < aSize; i++)
{
aTargetMask[i] &= aMask[i];
}
}
void BitSetUtils::Subtract(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize)
{
for (uint16_t i = 0; i < aSize; i++)
{
aTargetMask[i] &= (~aMask[i]);
}
}
void BitSetUtils::Copy(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize, uint16_t aNumBits)
{
for (uint16_t i = 0; i < aSize; i++)
{
aTargetMask[i] = aMask[i];
}
Tidy(aTargetMask, aSize, aNumBits);
}
void BitSetUtils::Tidy(uint8_t *aTargetMask, uint16_t aSize, uint16_t aNumBits)
{
// `Tidy` clears any unused bits in the last byte of the mask.
//
// Since bits are allocated starting from the MSB of the first byte
// (byte 0), the unused bits will be the lowest bits of the last
// byte (byte `aSize - 1`). The number of unused bits is calculated
// as `(aSize * kBitsPerByte) - aNumBits`.
aTargetMask[aSize - 1] &= static_cast<uint8_t>(~(static_cast<uint8_t>(1 << (aSize * kBitsPerByte - aNumBits)) - 1));
}
Error BitSetUtils::AppendMask(Message &aMessage, const uint8_t *aMask, uint16_t aSize)
{
return aMessage.AppendBytes(aMask, aSize);
}
Error BitSetUtils::ReadMask(const Message &aMessage,
const OffsetRange &aOffsetRange,
uint8_t *aTargetMask,
uint16_t aSize,
uint16_t aNumBits)
{
Error error;
SuccessOrExit(error = aMessage.Read(aOffsetRange, aTargetMask, aSize));
Tidy(aTargetMask, aSize, aNumBits);
exit:
return error;
}
} // namespace ot
+137 -16
View File
@@ -39,6 +39,7 @@
#include "common/bit_utils.hpp"
#include "common/clearable.hpp"
#include "common/equatable.hpp"
#include "common/error.hpp"
namespace ot {
@@ -51,13 +52,42 @@ namespace ot {
* @{
*/
class Message;
class OffsetRange;
class BitSetUtils
{
protected:
// Common helpers used by `BitSet<>` templates
static uint8_t BitMaskFor(uint16_t aIndex) { return (0x80 >> (aIndex & 7)); }
static bool IsAllZero(const uint8_t *aMask, uint16_t aSize);
static uint16_t CountBits(const uint8_t *aMask, uint16_t aSize);
static void FlipBits(uint8_t *aTargetMask, uint16_t aSize, uint16_t aNumBits);
static bool IsSubset(const uint8_t *aMask, const uint8_t *aSuperMask, uint16_t aSize);
static void Union(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize);
static void Subtract(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize);
static void Intersect(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize);
static void Copy(uint8_t *aTargetMask, const uint8_t *aMask, uint16_t aSize, uint16_t aNumBits);
static void Tidy(uint8_t *aTargetMask, uint16_t aSize, uint16_t aNumBits);
static Error AppendMask(Message &aMessage, const uint8_t *aMask, uint16_t aSize);
static Error ReadMask(const Message &aMessage,
const OffsetRange &aOffsetRange,
uint8_t *aTargetMask,
uint16_t aSize,
uint16_t aNumBits);
};
/**
* Represents a bit-set.
*
* @tparam kNumBits Specifies the number of bits.
*/
template <uint16_t kNumBits> class BitSet : public Equatable<BitSet<kNumBits>>, public Clearable<BitSet<kNumBits>>
template <uint16_t kNumBits>
class BitSet : public Equatable<BitSet<kNumBits>>, public Clearable<BitSet<kNumBits>>, private BitSetUtils
{
static_assert(kNumBits > 0, "kNumBits cannot be zero");
public:
/**
* Indicates whether a given bit index is contained in the set.
@@ -109,26 +139,117 @@ public:
* @retval TRUE If the set is empty.
* @retval FALSE If the set is not empty.
*/
bool IsEmpty(void) const
bool IsEmpty(void) const { return IsAllZero(mMask, kMaskSize); }
/**
* Returns the number of elements (bits set to TRUE) in the set.
*
* @returns The number of elements in the set.
*/
uint16_t CountElements(void) const { return CountBits(mMask, kMaskSize); }
/**
* Indicates whether the set is a subset of another set.
*
* @param[in] aOther The other set to check against.
*
* @retval TRUE If the set is a subset of @p aOther.
* @retval FALSE If the set is not a subset of @p aOther.
*/
bool IsSubsetOf(const BitSet<kNumBits> &aOther) const { return IsSubset(mMask, aOther.mMask, kMaskSize); }
/**
* Indicates whether the set is a superset of another set.
*
* @param[in] aOther The other set to check against.
*
* @retval TRUE If the set is a superset of @p aOther.
* @retval FALSE If the set is not a superset of @p aOther.
*/
bool IsSupersetOf(const BitSet<kNumBits> &aOther) const { return aOther.IsSubsetOf(*this); }
/**
* Complements the set (flips all bits in the mask).
*/
void Complement(void) { FlipBits(mMask, kMaskSize, kNumBits); }
/**
* Performs a union operation with another set.
*
* @param[in] aOther The other set.
*/
void UnionWith(const BitSet<kNumBits> &aOther) { Union(mMask, aOther.mMask, kMaskSize); }
/**
* Performs an intersection operation with another set.
*
* @param[in] aOther The other set.
*/
void IntersectWith(const BitSet<kNumBits> &aOther) { Intersect(mMask, aOther.mMask, kMaskSize); }
/**
* Performs a subtraction operation with another set.
*
* @param[in] aOther The other set.
*/
void SubtractWith(const BitSet<kNumBits> &aOther) { Subtract(mMask, aOther.mMask, kMaskSize); }
/**
* Gets a pointer to the byte array representing the bit mask.
*
* @returns A pointer to the byte array representing the bit mask.
*/
const uint8_t *GetMaskBytes(void) const { return mMask; }
/**
* Gets the size of the bit mask in bytes.
*
* @returns The size of the bit mask in bytes.
*/
uint16_t GetMaskSize(void) const { return kMaskSize; }
/**
* Gets the maximum number of bits in the set (`kNumBits` template parameter).
*
* @returns The maximum number of bits in the set.
*/
uint16_t GetNumBits(void) const { return kNumBits; }
/**
* Sets the bit mask from a given byte array.
*
* @param[in] aMask A pointer to the byte array to copy from. MUST contain proper size
*/
void SetMask(const uint8_t *aMask) { Copy(mMask, aMask, kMaskSize, kNumBits); }
/**
* Appends the bit mask bytes to a message.
*
* @param[in] aMessage The message to append to.
*
* @retval kErrorNone Successfully appended the bit mask.
* @retval kErrorNoBufs Insufficient available buffers to grow the message.
*/
Error AppendTo(Message &aMessage) const { return AppendMask(aMessage, mMask, kMaskSize); }
/**
* Reads the bit mask bytes from a message.
*
* @param[in] aMessage The message to read from.
* @param[in] aOffsetRange The offset range in the message to read from.
*
* @retval kErrorNone Successfully read the bit mask.
* @retval kErrorParse Failed to read the bit mask.
*/
Error ReadFrom(const Message &aMessage, const OffsetRange &aOffsetRange)
{
bool isEmpty = true;
for (uint8_t byte : mMask)
{
if (byte != 0)
{
isEmpty = false;
break;
}
}
return isEmpty;
return ReadMask(aMessage, aOffsetRange, mMask, kMaskSize, kNumBits);
}
private:
static uint8_t BitMaskFor(uint16_t aIndex) { return (0x80 >> (aIndex & 7)); }
static constexpr uint16_t kMaskSize = BytesForBitSize(kNumBits);
uint8_t mMask[BytesForBitSize(kNumBits)];
uint8_t mMask[kMaskSize];
};
/**
+1
View File
@@ -202,6 +202,7 @@ ot_unit_test(aes)
ot_unit_test(array)
ot_unit_test(binary_search)
ot_unit_test(bit_utils)
ot_unit_test(bit_set)
ot_unit_test(checksum)
ot_unit_test(child)
ot_unit_test(child_table)
+212
View File
@@ -0,0 +1,212 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <openthread/config.h>
#include "test_platform.h"
#include "test_util.hpp"
#include "common/bit_set.hpp"
namespace ot {
template <uint16_t kNumBits> void TestBitSetProperties(void)
{
BitSet<kNumBits> bitSet;
uint16_t i;
printf("TestBitSetProperties<%u>\r\n", kNumBits);
bitSet.Clear();
VerifyOrQuit(bitSet.IsEmpty());
VerifyOrQuit(bitSet.CountElements() == 0);
VerifyOrQuit(bitSet.GetNumBits() == kNumBits);
VerifyOrQuit(bitSet.GetMaskSize() == BytesForBitSize(kNumBits));
for (i = 0; i < kNumBits; i++)
{
VerifyOrQuit(!bitSet.Has(i));
}
bitSet.Add(0);
VerifyOrQuit(!bitSet.IsEmpty());
VerifyOrQuit(bitSet.CountElements() == 1);
VerifyOrQuit(bitSet.Has(0));
bitSet.Remove(0);
VerifyOrQuit(bitSet.IsEmpty());
VerifyOrQuit(!bitSet.Has(0));
for (i = 0; i < kNumBits; i++)
{
bitSet.Add(i);
VerifyOrQuit(bitSet.Has(i));
VerifyOrQuit(bitSet.CountElements() == i + 1);
}
bitSet.Clear();
VerifyOrQuit(bitSet.IsEmpty());
for (i = 0; i < kNumBits; i++)
{
VerifyOrQuit(!bitSet.Has(i));
}
bitSet.Update(0, true);
VerifyOrQuit(bitSet.Has(0));
bitSet.Update(0, false);
VerifyOrQuit(!bitSet.Has(0));
}
void TestBitSetOperations(void)
{
BitSet<10> set1;
BitSet<10> set2;
BitSet<10> unionSet;
BitSet<10> intersectSet;
BitSet<10> subtractSet;
printf("TestBitSetOperations\r\n");
set1.Clear();
set2.Clear();
VerifyOrQuit(set1.IsSubsetOf(set2));
VerifyOrQuit(set1.IsSupersetOf(set2));
VerifyOrQuit(set1 == set2);
set1.Add(1);
set1.Add(3);
set1.Add(8);
VerifyOrQuit(!set1.IsEmpty());
VerifyOrQuit(set1.CountElements() == 3);
VerifyOrQuit(!set1.IsSubsetOf(set2));
VerifyOrQuit(set2.IsSubsetOf(set1));
VerifyOrQuit(set1.IsSupersetOf(set2));
VerifyOrQuit(set1 != set2);
VerifyOrQuit(set1.IsSubsetOf(set1));
VerifyOrQuit(set1.IsSupersetOf(set1));
set2.Add(1);
set2.Add(8);
VerifyOrQuit(set2.IsSubsetOf(set1));
VerifyOrQuit(!set1.IsSubsetOf(set2));
VerifyOrQuit(set1.IsSupersetOf(set2));
unionSet = set1;
unionSet.UnionWith(set2);
VerifyOrQuit(unionSet == set1);
set2.Add(5);
unionSet = set1;
unionSet.UnionWith(set2);
VerifyOrQuit(unionSet.CountElements() == 4);
VerifyOrQuit(unionSet.Has(1) && unionSet.Has(3) && unionSet.Has(5) && unionSet.Has(8));
intersectSet = set1;
intersectSet.IntersectWith(set2);
VerifyOrQuit(intersectSet.CountElements() == 2);
VerifyOrQuit(intersectSet.Has(1) && intersectSet.Has(8));
VerifyOrQuit(!intersectSet.Has(3) && !intersectSet.Has(5));
subtractSet = set1;
subtractSet.SubtractWith(set2);
VerifyOrQuit(subtractSet.CountElements() == 1);
VerifyOrQuit(subtractSet.Has(3));
}
void TestBitSetComplementAndTidy(void)
{
BitSet<10> bitSet;
const uint8_t *bytes;
uint16_t i;
uint8_t dirtyMask[2];
printf("TestBitSetComplementAndTidy\r\n");
bitSet.Clear();
bitSet.Complement();
for (i = 0; i < 10; i++)
{
VerifyOrQuit(bitSet.Has(i));
}
bytes = bitSet.GetMaskBytes();
VerifyOrQuit(bitSet.GetMaskSize() == 2);
VerifyOrQuit(bytes[0] == 0xff);
VerifyOrQuit(bytes[1] == 0xc0);
bitSet.Complement();
VerifyOrQuit(bitSet.IsEmpty());
VerifyOrQuit(bytes[0] == 0x00);
VerifyOrQuit(bytes[1] == 0x00);
dirtyMask[0] = 0xff;
dirtyMask[1] = 0xff;
bitSet.SetMask(dirtyMask);
for (i = 0; i < 10; i++)
{
VerifyOrQuit(bitSet.Has(i));
}
VerifyOrQuit(bitSet.CountElements() == 10);
bytes = bitSet.GetMaskBytes();
VerifyOrQuit(bytes[0] == 0xff);
VerifyOrQuit(bytes[1] == 0xc0);
}
void TestBitSet(void)
{
TestBitSetProperties<1>();
TestBitSetProperties<7>();
TestBitSetProperties<8>();
TestBitSetProperties<9>();
TestBitSetProperties<16>();
TestBitSetProperties<17>();
TestBitSetProperties<100>();
TestBitSetOperations();
TestBitSetComplementAndTidy();
}
} // namespace ot
int main(void)
{
ot::TestBitSet();
printf("All tests passed\n");
return 0;
}