[meshcop] adding SteeringData::MergeBloomFilterWith() (#12145)

This commit adds a new method, `MergeBloomFilterWith()`, to
`SteeringData` to allow combining two Bloom filters.

The method performs a bitwise OR operation between the current
Steering Data Bloom filter and a given one. It handles cases where
the given filter to merge has a shorter length than the target Bloom
filter. It requires the target filter's length to be a multiple of
the source's length.

A new unit test, `TestSteeringDataBloomFilterMerge()`, is included to
validate the merge logic with various filter length combinations.
This commit is contained in:
Abtin Keshavarzian
2025-11-20 22:10:56 -08:00
committed by GitHub
parent 9483553c44
commit 041c25acbb
3 changed files with 216 additions and 38 deletions
+41 -1
View File
@@ -43,6 +43,9 @@ RegisterLogModule("MeshCoP");
namespace MeshCoP {
//---------------------------------------------------------------------------------------------------------------------
// JoinerPskd
Error JoinerPskd::SetFrom(const char *aPskdString)
{
Error error = kErrorNone;
@@ -99,6 +102,9 @@ exit:
return valid;
}
//---------------------------------------------------------------------------------------------------------------------
// JoinerDiscerner
void JoinerDiscerner::GenerateJoinerId(Mac::ExtAddress &aJoinerId) const
{
aJoinerId.GenerateRandom();
@@ -182,6 +188,9 @@ JoinerDiscerner::InfoString JoinerDiscerner::ToString(void) const
return string;
}
//---------------------------------------------------------------------------------------------------------------------
// SteeringData
void SteeringData::Init(uint8_t aLength)
{
OT_ASSERT(aLength <= kMaxLength);
@@ -213,12 +222,30 @@ void SteeringData::UpdateBloomFilter(const JoinerDiscerner &aDiscerner)
void SteeringData::UpdateBloomFilter(const HashBitIndexes &aIndexes)
{
OT_ASSERT((mLength > 0) && (mLength <= kMaxLength));
OT_ASSERT(IsLengthValid());
SetBit(aIndexes.mIndex[0] % GetNumBits());
SetBit(aIndexes.mIndex[1] % GetNumBits());
}
Error SteeringData::MergeBloomFilterWith(const SteeringData &aOther)
{
Error error = kErrorNone;
VerifyOrExit(IsLengthValid(), error = kErrorInvalidArgs);
VerifyOrExit(aOther.IsLengthValid(), error = kErrorInvalidArgs);
VerifyOrExit(GetLength() % aOther.GetLength() == 0, error = kErrorInvalidArgs);
for (uint8_t index = 0; index < GetLength(); index++)
{
m8[index] |= aOther.m8[index % aOther.GetLength()];
}
exit:
return error;
}
bool SteeringData::Contains(const Mac::ExtAddress &aJoinerId) const
{
HashBitIndexes indexes;
@@ -274,6 +301,19 @@ bool SteeringData::DoesAllMatch(uint8_t aMatch) const
return matches;
}
SteeringData::InfoString SteeringData::ToString(void) const
{
InfoString string;
string.Append("[");
string.AppendHexBytes(GetData(), Min(GetLength(), kMaxLength));
string.Append("]");
return string;
}
//---------------------------------------------------------------------------------------------------------------------
void ComputeJoinerId(const Mac::ExtAddress &aEui64, Mac::ExtAddress &aJoinerId)
{
Crypto::Sha256 sha256;
+31
View File
@@ -46,6 +46,7 @@
#include "common/equatable.hpp"
#include "common/log.hpp"
#include "common/message.hpp"
#include "common/num_utils.hpp"
#include "common/numeric_limits.hpp"
#include "common/string.hpp"
#include "mac/mac_types.hpp"
@@ -229,8 +230,16 @@ private:
class SteeringData : public otSteeringData
{
public:
static constexpr uint8_t kMinLength = 1; ///< Minimum Steering Data length (in bytes).
static constexpr uint8_t kMaxLength = OT_STEERING_DATA_MAX_LENGTH; ///< Maximum Steering Data length (in bytes).
static constexpr uint16_t kInfoStringSize = 45; ///< Size of `InfoString` to use with `ToString()`.
/**
* Defines the fixed-length `String` object returned from `ToString()`.
*/
typedef String<kInfoStringSize> InfoString;
/**
* Represents the hash bit index values for the bloom filter calculated from a Joiner ID.
*
@@ -299,6 +308,20 @@ public:
*/
void UpdateBloomFilter(const JoinerDiscerner &aDiscerner);
/**
* Merges the bloom filter by combining it with another steering data filter.
*
* Both bloom filters must have valid lengths (non-zero and not exceeding `kMaxLength`).
*
* The bloom filter from @p aOther must have a length that is a divisor of the current filter's length.
*
* @param[in] aOther The other bloom filter to combine with current filter.
*
* @retval kErrorNone Successfully merged @p aOther into the current bloom filter.
* @retval kErrorInvalidArgs The filter lengths are not valid or they cannot be merged.
*/
Error MergeBloomFilterWith(const SteeringData &aOther);
/**
* Indicates whether the bloom filter is empty (all the bits are cleared).
*
@@ -340,6 +363,13 @@ public:
*/
bool Contains(const HashBitIndexes &aIndexes) const;
/**
* Converts the Steering Data to a human-readable string representation.
*
* @returns An `InfoString` representation of the Steering Data.
*/
InfoString ToString(void) const;
/**
* Calculates the bloom filter hash bit indexes from a given Joiner ID.
*
@@ -363,6 +393,7 @@ public:
private:
static constexpr uint8_t kPermitAll = 0xff;
bool IsLengthValid(void) const { return IsValueInRange(mLength, kMinLength, kMaxLength); }
uint8_t GetNumBits(void) const { return (mLength * kBitsPerByte); }
uint8_t BitIndex(uint8_t aBit) const { return (mLength - 1 - (aBit / kBitsPerByte)); }
+144 -37
View File
@@ -54,65 +54,170 @@ void TestSteeringData(void)
steeringData.SetToPermitAllJoiners();
DumpBuffer("After SetToPermitAllJoiners()", steeringData.GetData(), steeringData.GetLength());
VerifyOrQuit(steeringData.GetLength() == 1, "after SetToPermitAllJoiners()");
VerifyOrQuit(steeringData.PermitsAllJoiners(), "after SetToPermitAllJoiners()");
VerifyOrQuit(!steeringData.IsEmpty(), "after SetToPermitAllJoiners()");
VerifyOrQuit(steeringData.Contains(joinerId1), "after SetToPermitAllJoiners()");
VerifyOrQuit(steeringData.Contains(joinerId2), "after SetToPermitAllJoiners()");
VerifyOrQuit(steeringData.Contains(indexes), "after SetToPermitAllJoiners()");
VerifyOrQuit(steeringData.GetLength() == 1);
VerifyOrQuit(steeringData.PermitsAllJoiners());
VerifyOrQuit(!steeringData.IsEmpty());
VerifyOrQuit(steeringData.Contains(joinerId1));
VerifyOrQuit(steeringData.Contains(joinerId2));
VerifyOrQuit(steeringData.Contains(indexes));
steeringData.Clear();
DumpBuffer("After Clear()", steeringData.GetData(), steeringData.GetLength());
VerifyOrQuit(steeringData.GetLength() == 1, "after Clear()");
VerifyOrQuit(!steeringData.PermitsAllJoiners(), "after Clear()");
VerifyOrQuit(steeringData.IsEmpty(), "after Clear()");
VerifyOrQuit(!steeringData.Contains(joinerId1), "after Clear()");
VerifyOrQuit(!steeringData.Contains(joinerId2), "after Clear()");
VerifyOrQuit(!steeringData.Contains(indexes), "after Clear()");
VerifyOrQuit(steeringData.GetLength() == 1);
VerifyOrQuit(!steeringData.PermitsAllJoiners());
VerifyOrQuit(steeringData.IsEmpty());
VerifyOrQuit(!steeringData.Contains(joinerId1));
VerifyOrQuit(!steeringData.Contains(joinerId2));
VerifyOrQuit(!steeringData.Contains(indexes));
for (uint8_t len = 1; len <= MeshCoP::SteeringData::kMaxLength; len++)
for (uint8_t len = MeshCoP::SteeringData::kMinLength; len <= MeshCoP::SteeringData::kMaxLength; len++)
{
printf("\n--------------------------------------------");
steeringData.Init(len);
VerifyOrQuit(steeringData.GetLength() == len, "after Init()");
VerifyOrQuit(steeringData.IsEmpty(), "IsEmpy() failed after Init()");
VerifyOrQuit(!steeringData.PermitsAllJoiners(), "after Init()");
VerifyOrQuit(!steeringData.Contains(joinerId1), "after Init()");
VerifyOrQuit(!steeringData.Contains(joinerId2), "after Init()");
VerifyOrQuit(!steeringData.Contains(indexes), "after Init()");
VerifyOrQuit(steeringData.GetLength() == len);
VerifyOrQuit(steeringData.IsEmpty());
VerifyOrQuit(!steeringData.PermitsAllJoiners());
VerifyOrQuit(!steeringData.Contains(joinerId1));
VerifyOrQuit(!steeringData.Contains(joinerId2));
VerifyOrQuit(!steeringData.Contains(indexes));
steeringData.UpdateBloomFilter(joinerId1);
DumpBuffer("After UpdateBloomFilter(joinerId1)", steeringData.GetData(), steeringData.GetLength());
VerifyOrQuit(steeringData.GetLength() == len, "after UpdateBloomFilter()");
VerifyOrQuit(!steeringData.IsEmpty(), "IsEmpy() failed after UpdateBloomFilter()");
VerifyOrQuit(!steeringData.PermitsAllJoiners(), "after UpdateBloomFilter");
VerifyOrQuit(steeringData.Contains(joinerId1), "after UpdateBloomFilter");
printf("\nAfter UpdateBloomFilter(joinerId1): %s", steeringData.ToString().AsCString());
VerifyOrQuit(steeringData.GetLength() == len);
VerifyOrQuit(!steeringData.IsEmpty());
VerifyOrQuit(!steeringData.PermitsAllJoiners());
VerifyOrQuit(steeringData.Contains(joinerId1));
steeringData.UpdateBloomFilter(joinerId2);
DumpBuffer("After UpdateBloomFilter(joinerId2)", steeringData.GetData(), steeringData.GetLength());
VerifyOrQuit(steeringData.GetLength() == len, "after UpdateBloomFilter()");
VerifyOrQuit(!steeringData.IsEmpty(), "IsEmpy() failed after UpdateBloomFilter()");
VerifyOrQuit(!steeringData.PermitsAllJoiners(), "after UpdateBloomFilter");
VerifyOrQuit(steeringData.Contains(joinerId1), "after UpdateBloomFilter");
VerifyOrQuit(steeringData.Contains(joinerId2), "after UpdateBloomFilter");
VerifyOrQuit(steeringData.Contains(indexes), "after UpdateBloomFilter");
printf("\nAfter UpdateBloomFilter(joinerId2): %s", steeringData.ToString().AsCString());
VerifyOrQuit(steeringData.GetLength() == len);
VerifyOrQuit(!steeringData.IsEmpty());
VerifyOrQuit(!steeringData.PermitsAllJoiners());
VerifyOrQuit(steeringData.Contains(joinerId1));
VerifyOrQuit(steeringData.Contains(joinerId2));
VerifyOrQuit(steeringData.Contains(indexes));
}
steeringData.Init(0);
VerifyOrQuit(steeringData.GetLength() == 0, "after Init()");
VerifyOrQuit(steeringData.IsEmpty(), "IsEmpy() failed after Init()");
VerifyOrQuit(!steeringData.PermitsAllJoiners(), "after Init()");
VerifyOrQuit(!steeringData.Contains(joinerId1), "after Init()");
VerifyOrQuit(!steeringData.Contains(joinerId2), "after Init()");
VerifyOrQuit(!steeringData.Contains(indexes), "after Init()");
VerifyOrQuit(steeringData.GetLength() == 0);
VerifyOrQuit(steeringData.IsEmpty());
VerifyOrQuit(!steeringData.PermitsAllJoiners());
VerifyOrQuit(!steeringData.Contains(joinerId1));
VerifyOrQuit(!steeringData.Contains(joinerId2));
VerifyOrQuit(!steeringData.Contains(indexes));
printf("TestSteeringData() passed\n");
}
void TestSteeringDataBloomFilterMerge(void)
{
static constexpr uint8_t kMaxJoinerIds = 3;
static const uint8_t kTestSteeringDataLengths[] = {16, 8, 4, 2, 1};
Instance *instance;
MeshCoP::SteeringData steeringData;
MeshCoP::SteeringData otherSteeringData;
Mac::ExtAddress joinerIds[kMaxJoinerIds];
Mac::ExtAddress otherJoinerIds[kMaxJoinerIds];
instance = static_cast<Instance *>(testInitInstance());
VerifyOrQuit(instance != nullptr);
printf("\n--------------------------------------------");
printf("\nTestSteeringDataBloomFilterMerge()");
printf("\nJoiner IDs for SteeringData");
for (Mac::ExtAddress &id : joinerIds)
{
uint8_t buffer[sizeof(Mac::ExtAddress)];
Random::NonCrypto::FillBuffer(buffer, sizeof(buffer));
id.Set(buffer);
printf("\n %s", id.ToString().AsCString());
}
printf("\n--------------------------------------------");
printf("\nJoiner IDs for OtherSteeringData");
for (Mac::ExtAddress &id : otherJoinerIds)
{
uint8_t buffer[sizeof(Mac::ExtAddress)];
Random::NonCrypto::FillBuffer(buffer, sizeof(buffer));
id.Set(buffer);
printf("\n %s", id.ToString().AsCString());
}
for (uint8_t length : kTestSteeringDataLengths)
{
for (uint8_t otherLength : kTestSteeringDataLengths)
{
printf("\n--------------------------------------------");
printf("\nLen:%u OtherLen:%u", length, otherLength);
steeringData.Init(length);
otherSteeringData.Init(otherLength);
VerifyOrQuit(steeringData.GetLength() == length);
VerifyOrQuit(otherSteeringData.GetLength() == otherLength);
VerifyOrQuit(steeringData.IsEmpty());
VerifyOrQuit(otherSteeringData.IsEmpty());
if (otherLength > length)
{
VerifyOrQuit(steeringData.MergeBloomFilterWith(otherSteeringData) != kErrorNone);
continue;
}
SuccessOrQuit(steeringData.MergeBloomFilterWith(otherSteeringData));
VerifyOrQuit(steeringData.GetLength() == length);
VerifyOrQuit(otherSteeringData.GetLength() == otherLength);
VerifyOrQuit(steeringData.IsEmpty());
VerifyOrQuit(otherSteeringData.IsEmpty());
for (Mac::ExtAddress &id : joinerIds)
{
steeringData.UpdateBloomFilter(id);
VerifyOrQuit(steeringData.Contains(id));
}
for (Mac::ExtAddress &id : otherJoinerIds)
{
otherSteeringData.UpdateBloomFilter(id);
VerifyOrQuit(otherSteeringData.Contains(id));
}
printf("\nSteering Data : %s", steeringData.ToString().AsCString());
printf("\nOther Steering Data : %s", otherSteeringData.ToString().AsCString());
SuccessOrQuit(steeringData.MergeBloomFilterWith(otherSteeringData));
printf("\nMerged Steering Data: %s", steeringData.ToString().AsCString());
VerifyOrQuit(steeringData.GetLength() == length);
for (const Mac::ExtAddress &id : joinerIds)
{
VerifyOrQuit(steeringData.Contains(id));
}
for (const Mac::ExtAddress &id : otherJoinerIds)
{
VerifyOrQuit(steeringData.Contains(id));
}
}
}
printf("\nTestSteeringDataBloomFilterMerge() passed\n");
}
void TestTimestamp(void)
{
MeshCoP::Timestamp t1;
@@ -204,7 +309,9 @@ void TestTimestamp(void)
int main(void)
{
ot::TestSteeringData();
ot::TestSteeringDataBloomFilterMerge();
ot::TestTimestamp();
printf("\nAll tests passed.\n");
return 0;
}