diff --git a/src/core/meshcop/meshcop.cpp b/src/core/meshcop/meshcop.cpp index daa057699..945ca021e 100644 --- a/src/core/meshcop/meshcop.cpp +++ b/src/core/meshcop/meshcop.cpp @@ -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; diff --git a/src/core/meshcop/meshcop.hpp b/src/core/meshcop/meshcop.hpp index 5ca504f14..5b6b34b2a 100644 --- a/src/core/meshcop/meshcop.hpp +++ b/src/core/meshcop/meshcop.hpp @@ -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 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)); } diff --git a/tests/unit/test_meshcop.cpp b/tests/unit/test_meshcop.cpp index 04480fec2..fb52e298e 100644 --- a/tests/unit/test_meshcop.cpp +++ b/tests/unit/test_meshcop.cpp @@ -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(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; }