[mac] enhance Mac::Filter implementation to use Array (#13584)

This commit refactors `Mac::Filter` to use `Array<Entry, kMaxEntries>`
instead of a fixed-size C-style array.

This enhances lookup and iteration performance by maintaining entries
contiguously, avoiding iteration over empty slots up to `kMaxEntries`.

It also simplifies the implementation and eliminates duplicate code
by unifying entry addition/update (`AddOrUpdateEntry()`), removal
(`RemoveEntry()`), and iteration (`GetNext()`) across both address
filtering and RSS-In filtering.

Additionally, this commit introduces comprehensive unit tests in
`tests/unit/test_mac_filter.cpp` covering:
- Filter modes (allowlist, denylist, RSS-In only) and transitions.
- Address and RSS-In addition, updating, removal, and default RSS-In.
- Entry iteration and array compaction after deletions.
- Coexistence and independent lifecycles of address and RSS-In filters.
- Capacity limits (`kErrorNoBufs`) and entry slot reuse.
This commit is contained in:
Abtin Keshavarzian
2026-09-06 23:00:05 +02:00
committed by GitHub
parent 8e334caa2e
commit 805ab3276d
4 changed files with 829 additions and 167 deletions
+98 -132
View File
@@ -40,170 +40,93 @@
namespace ot {
namespace Mac {
//---------------------------------------------------------------------------------------------------------------------
// Filter
Filter::Filter(void)
: mMode(kModeRssInOnly)
, mDefaultRssIn(kFixedRssDisabled)
{
for (FilterEntry &entry : mFilterEntries)
{
entry.mFiltered = false;
entry.mRssIn = kFixedRssDisabled;
}
}
const Filter::FilterEntry *Filter::FindEntry(const ExtAddress &aExtAddress) const
Error Filter::AddOrUpdateEntry(Type aType, const ExtAddress &aExtAddress, int8_t aRss)
{
const FilterEntry *rval = nullptr;
for (const FilterEntry &entry : mFilterEntries)
{
if (entry.IsInUse() && (aExtAddress == entry.mExtAddress))
{
ExitNow(rval = &entry);
}
}
exit:
return rval;
}
Filter::FilterEntry *Filter::FindAvailableEntry(void)
{
FilterEntry *rval = nullptr;
for (FilterEntry &entry : mFilterEntries)
{
if (!entry.IsInUse())
{
ExitNow(rval = &entry);
}
}
exit:
return rval;
}
Error Filter::AddAddress(const ExtAddress &aExtAddress)
{
Error error = kErrorNone;
FilterEntry *entry = FindEntry(aExtAddress);
Error error = kErrorNone;
Entry *entry = mEntries.FindMatching(aExtAddress);
if (entry == nullptr)
{
VerifyOrExit((entry = FindAvailableEntry()) != nullptr, error = kErrorNoBufs);
entry->mExtAddress = aExtAddress;
}
entry->mFiltered = true;
exit:
return error;
}
void Filter::RemoveAddress(const ExtAddress &aExtAddress)
{
FilterEntry *entry = FindEntry(aExtAddress);
if (entry != nullptr)
{
entry->mFiltered = false;
}
}
void Filter::ClearAddresses(void)
{
for (FilterEntry &entry : mFilterEntries)
{
entry.mFiltered = false;
}
}
Error Filter::GetNextAddress(Iterator &aIterator, Entry &aEntry) const
{
Error error = kErrorNotFound;
for (; aIterator < GetArrayLength(mFilterEntries); aIterator++)
{
const FilterEntry &entry = mFilterEntries[aIterator];
if (entry.mFiltered)
{
aEntry.mExtAddress = entry.mExtAddress;
aEntry.mRssIn = entry.mRssIn;
error = kErrorNone;
aIterator++;
break;
}
}
return error;
}
Error Filter::AddRssIn(const ExtAddress &aExtAddress, int8_t aRss)
{
Error error = kErrorNone;
FilterEntry *entry = FindEntry(aExtAddress);
if (entry == nullptr)
{
entry = FindAvailableEntry();
entry = mEntries.PushBack();
VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
entry->mExtAddress = aExtAddress;
entry->Init(aExtAddress);
}
entry->mRssIn = aRss;
if (aType == kAddrFilter)
{
entry->SetInAddrFilter(true);
}
else
{
entry->SetRssIn(aRss);
}
exit:
return error;
}
void Filter::RemoveRssIn(const ExtAddress &aExtAddress)
void Filter::RemoveEntry(Type aType, const ExtAddress &aExtAddress)
{
FilterEntry *entry = FindEntry(aExtAddress);
Entry *entry = mEntries.FindMatching(aExtAddress);
VerifyOrExit(entry != nullptr);
entry->mRssIn = kFixedRssDisabled;
if (aType == kAddrFilter)
{
entry->SetInAddrFilter(false);
}
else
{
entry->ClearRssIn();
}
if (entry->Matches(Entry::kNotInUse))
{
mEntries.Remove(*entry);
}
exit:
return;
}
void Filter::ClearAllRssIn(void)
{
for (FilterEntry &entry : mFilterEntries)
{
entry.mRssIn = kFixedRssDisabled;
}
mDefaultRssIn = kFixedRssDisabled;
}
Error Filter::GetNextRssIn(Iterator &aIterator, Entry &aEntry) const
Error Filter::GetNext(Type aType, Iterator &aIterator, EntryInfo &aInfo) const
{
Error error = kErrorNotFound;
for (; aIterator < GetArrayLength(mFilterEntries); aIterator++)
for (; aIterator < mEntries.GetLength(); aIterator++)
{
const FilterEntry &entry = mFilterEntries[aIterator];
const Entry &entry = mEntries[aIterator];
if (entry.mRssIn != kFixedRssDisabled)
if ((aType == kAddrFilter) ? entry.IsInAddrFilter() : entry.IsInRssFilter())
{
aEntry.mExtAddress = entry.mExtAddress;
aEntry.mRssIn = entry.mRssIn;
error = kErrorNone;
aInfo.mExtAddress = entry.GetExtAddress();
aInfo.mRssIn = entry.GetRssIn();
error = kErrorNone;
aIterator++;
ExitNow();
}
}
VerifyOrExit(aType == kRssFilter);
// Return the default RssIn at the end of list
if ((aIterator == GetArrayLength(mFilterEntries)) && (mDefaultRssIn != kFixedRssDisabled))
VerifyOrExit(aIterator == mEntries.GetLength());
if (mDefaultRssIn != kFixedRssDisabled)
{
AsCoreType(&aEntry.mExtAddress).Fill(0xff);
aEntry.mRssIn = mDefaultRssIn;
error = kErrorNone;
AsCoreType(&aInfo.mExtAddress).Fill(0xff);
aInfo.mRssIn = mDefaultRssIn;
error = kErrorNone;
aIterator++;
}
@@ -211,11 +134,44 @@ exit:
return error;
}
void Filter::ClearAll(Type aType)
{
for (Entry &entry : mEntries)
{
if (aType == kAddrFilter)
{
entry.SetInAddrFilter(false);
}
else
{
entry.ClearRssIn();
}
}
if (aType == kRssFilter)
{
mDefaultRssIn = kFixedRssDisabled;
}
mEntries.RemoveAllMatching(Entry::kNotInUse);
}
Error Filter::AddRssIn(const ExtAddress &aExtAddress, int8_t aRss)
{
Error error = kErrorNone;
VerifyOrExit(aRss != kFixedRssDisabled, error = kErrorInvalidArgs);
error = AddOrUpdateEntry(kRssFilter, aExtAddress, aRss);
exit:
return error;
}
Error Filter::Apply(const ExtAddress &aExtAddress, int8_t &aRss) const
{
Error error = kErrorNone;
const FilterEntry *entry = FindEntry(aExtAddress);
bool isInFilterList;
Error error = kErrorNone;
const Entry *entry = mEntries.FindMatching(aExtAddress);
bool isInAddrFilterList;
// Use the default RssIn setting for all receiving messages first.
aRss = mDefaultRssIn;
@@ -223,7 +179,7 @@ Error Filter::Apply(const ExtAddress &aExtAddress, int8_t &aRss) const
// In allowlist mode, entry must be present in the list, in
// denylist mode it must not be present.
isInFilterList = (entry != nullptr) && entry->mFiltered;
isInAddrFilterList = (entry != nullptr) && entry->IsInAddrFilter();
switch (mMode)
{
@@ -231,17 +187,17 @@ Error Filter::Apply(const ExtAddress &aExtAddress, int8_t &aRss) const
break;
case kModeAllowlist:
VerifyOrExit(isInFilterList, error = kErrorAddressFiltered);
VerifyOrExit(isInAddrFilterList, error = kErrorAddressFiltered);
break;
case kModeDenylist:
VerifyOrExit(!isInFilterList, error = kErrorAddressFiltered);
VerifyOrExit(!isInAddrFilterList, error = kErrorAddressFiltered);
break;
}
if ((entry != nullptr) && (entry->mRssIn != kFixedRssDisabled))
if ((entry != nullptr) && entry->IsInRssFilter())
{
aRss = entry->mRssIn;
aRss = entry->GetRssIn();
}
exit:
@@ -270,6 +226,16 @@ exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// Filter::Entry
void Filter::Entry::Init(const ExtAddress &aExtAddress)
{
mExtAddress = aExtAddress;
mInAddrFilter = false;
mRssIn = kFixedRssDisabled;
}
} // namespace Mac
} // namespace ot
+63 -35
View File
@@ -40,8 +40,8 @@
#include <stdint.h>
#include "common/array.hpp"
#include "common/as_core_type.hpp"
#include "common/const_cast.hpp"
#include "common/non_copyable.hpp"
#include "mac/mac_frame.hpp"
@@ -58,15 +58,15 @@ namespace Mac {
*/
/**
* Implements Mac Filter on IEEE 802.15.4 frames.
* Implements MAC Filter on IEEE 802.15.4 frames.
*/
class Filter : private NonCopyable
{
public:
/**
* Represents a Mac Filter entry (used during iteration).
* Represents MAC Filter entry information.
*/
typedef otMacFilterEntry Entry;
typedef otMacFilterEntry EntryInfo;
/**
* Represents an iterator used to iterate through filter entries.
@@ -76,7 +76,7 @@ public:
typedef otMacFilterIterator Iterator;
/**
* Type represents the MAC Filter mode.
* Represents the MAC Filter mode.
*/
enum Mode : uint8_t
{
@@ -85,7 +85,8 @@ public:
kModeDenylist = OT_MAC_FILTER_ADDRESS_MODE_DENYLIST, ///< Enable denylist address filter mode.
};
static constexpr int8_t kFixedRssDisabled = OT_MAC_FILTER_FIXED_RSS_DISABLED; ///< Value when no fixed RSS is set.
static constexpr int8_t kFixedRssDisabled = OT_MAC_FILTER_FIXED_RSS_DISABLED; ///< Value when no fixed RSS is set.
static constexpr Iterator kIteratorInit = OT_MAC_FILTER_ITERATOR_INIT; ///< Initializer for `Iterator`.
/**
* Initializes the filter.
@@ -95,7 +96,7 @@ public:
/**
* Gets the MAC Filter mode.
*
* @returns the Filter mode.
* @returns The Filter mode.
*/
Mode GetMode(void) const { return mMode; }
@@ -114,7 +115,10 @@ public:
* @retval kErrorNone Successfully added @p aExtAddress to the filter.
* @retval kErrorNoBufs No available entry exists.
*/
Error AddAddress(const ExtAddress &aExtAddress);
Error AddAddress(const ExtAddress &aExtAddress)
{
return AddOrUpdateEntry(kAddrFilter, aExtAddress, kFixedRssDisabled);
}
/**
* Removes an Extended Address from the filter.
@@ -123,33 +127,34 @@ public:
*
* @param[in] aExtAddress A reference to the Extended Address to remove.
*/
void RemoveAddress(const ExtAddress &aExtAddress);
void RemoveAddress(const ExtAddress &aExtAddress) { RemoveEntry(kAddrFilter, aExtAddress); }
/**
* Clears all Extended Addresses from the filter.
*/
void ClearAddresses(void);
void ClearAddresses(void) { ClearAll(kAddrFilter); }
/**
* Iterates through filter entries.
*
* @param[in,out] aIterator A reference to the MAC filter iterator context.
* To get the first in-use address filter, set it to OT_MAC_FILTER_ITERATOR_INIT.
* @param[out] aEntry A reference to where the information is placed.
* To get the first in-use address filter, set it to `kIteratorInit`.
* @param[out] aInfo A reference to where the entry information is placed.
*
* @retval kErrorNone Successfully retrieved the next address filter entry.
* @retval kErrorNotFound No subsequent entry exists.
*/
Error GetNextAddress(Iterator &aIterator, Entry &aEntry) const;
Error GetNextAddress(Iterator &aIterator, EntryInfo &aInfo) const { return GetNext(kAddrFilter, aIterator, aInfo); }
/**
* Adds a fixed received signal strength entry for the messages from a given Extended Address.
*
* @param[in] aExtAddress An Extended Address
* @param[in] aExtAddress An Extended Address.
* @param[in] aRss The received signal strength to set.
*
* @retval kErrorNone Successfully set @p aRss for @p aExtAddress.
* @retval kErrorNoBufs No available entry exists.
* @retval kErrorNone Successfully set @p aRss for @p aExtAddress.
* @retval kErrorInvalidArgs @p aRss is not valid (equal to `kFixedRssDisabled`).
* @retval kErrorNoBufs No available entry exists.
*/
Error AddRssIn(const ExtAddress &aExtAddress, int8_t aRss);
@@ -158,9 +163,9 @@ public:
*
* No action is performed if there is no existing entry in the filter list matching the given Extended Address.
*
* @param[in] aExtAddress A Extended Address.
* @param[in] aExtAddress An Extended Address.
*/
void RemoveRssIn(const ExtAddress &aExtAddress);
void RemoveRssIn(const ExtAddress &aExtAddress) { RemoveEntry(kRssFilter, aExtAddress); }
/**
* Sets the default received signal strength.
@@ -180,21 +185,21 @@ public:
/**
* Clears all the received signal strength settings (including the default RSS-In).
*/
void ClearAllRssIn(void);
void ClearAllRssIn(void) { ClearAll(kRssFilter); }
/**
* Iterates through RssIn filter entry.
*
* @param[in,out] aIterator A reference to the MAC filter iterator context. To get the first in-use RssIn
* filter entry, it should be set to OT_MAC_FILTER_ITERATOR_INIT.
* @param[out] aEntry A reference to where the information is placed. The last entry would have the
* filter entry, it should be set to `kIteratorInit`.
* @param[out] aInfo A reference to where the entry information is placed. The last entry would have the
* Extended Address as all 0xff to indicate the default received signal strength
* if it was set.
*
* @retval kErrorNone Successfully retrieved the next RssIn filter entry.
* @retval kErrorNotFound No subsequent entry exists.
*/
Error GetNextRssIn(Iterator &aIterator, Entry &aEntry) const;
Error GetNextRssIn(Iterator &aIterator, EntryInfo &aInfo) const { return GetNext(kRssFilter, aIterator, aInfo); }
/**
* Applies the filter rules on a given Extended Address.
@@ -224,24 +229,47 @@ public:
Error ApplyToRxFrame(RxFrame &aRxFrame, const ExtAddress &aExtAddress, Neighbor *aNeighbor = nullptr) const;
private:
static constexpr uint16_t kMaxEntries = OPENTHREAD_CONFIG_MAC_FILTER_SIZE;
static constexpr uint8_t kMaxEntries = OPENTHREAD_CONFIG_MAC_FILTER_SIZE;
struct FilterEntry
enum Type : bool
{
bool mFiltered; // Indicates whether or not this entry is filtered (allowlist/denylist modes).
int8_t mRssIn; // The RssIn value for this entry or `kFixedRssDisabled`.
ExtAddress mExtAddress; // IEEE 802.15.4 Extended Address.
bool IsInUse(void) const { return mFiltered || (mRssIn != kFixedRssDisabled); }
kRssFilter,
kAddrFilter,
};
FilterEntry *FindAvailableEntry(void);
const FilterEntry *FindEntry(const ExtAddress &aExtAddress) const;
FilterEntry *FindEntry(const ExtAddress &aExtAddress) { return AsNonConst(AsConst(this)->FindEntry(aExtAddress)); }
class Entry
{
public:
enum NotInUseMatcher : uint8_t
{
kNotInUse,
};
Mode mMode;
int8_t mDefaultRssIn;
FilterEntry mFilterEntries[kMaxEntries];
void Init(const ExtAddress &aExtAddress);
const ExtAddress &GetExtAddress(void) const { return mExtAddress; }
bool IsInAddrFilter(void) const { return mInAddrFilter; }
void SetInAddrFilter(bool aInAddrFilter) { mInAddrFilter = aInAddrFilter; }
bool IsInRssFilter(void) const { return (mRssIn != kFixedRssDisabled); }
int8_t GetRssIn(void) const { return mRssIn; }
void SetRssIn(int8_t aRss) { mRssIn = aRss; }
void ClearRssIn(void) { mRssIn = kFixedRssDisabled; }
bool Matches(const ExtAddress &aExtAddress) const { return mExtAddress == aExtAddress; }
bool Matches(NotInUseMatcher) const { return !IsInAddrFilter() && !IsInRssFilter(); }
private:
ExtAddress mExtAddress;
bool mInAddrFilter;
int8_t mRssIn;
};
Error AddOrUpdateEntry(Type aType, const ExtAddress &aExtAddress, int8_t aRss);
void RemoveEntry(Type aType, const ExtAddress &aExtAddress);
Error GetNext(Type aType, Iterator &aIterator, EntryInfo &aInfo) const;
void ClearAll(Type aType);
Array<Entry, kMaxEntries> mEntries;
Mode mMode;
int8_t mDefaultRssIn;
};
/**
+1
View File
@@ -234,6 +234,7 @@ ot_unit_test(link_quality)
ot_unit_test(linked_list)
ot_unit_test(lowpan)
ot_unit_test(ltv)
ot_unit_test(mac_filter)
ot_unit_test(mac_frame)
ot_unit_test(macros)
ot_unit_test(mdns)
+667
View File
@@ -0,0 +1,667 @@
/*
* 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 "test_platform.h"
#include "test_util.h"
#include <openthread/config.h>
#include "common/as_core_type.hpp"
#include "common/clearable.hpp"
#include "common/code_utils.hpp"
#include "common/encoding.hpp"
#include "mac/mac_filter.hpp"
namespace ot {
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
static Mac::ExtAddress MakeExtAddress(uint16_t aIndex)
{
Mac::ExtAddress addr;
ClearAllBytes(addr);
BigEndian::WriteUint16(aIndex, &addr.m8[sizeof(Mac::ExtAddress) - sizeof(uint16_t)]);
return addr;
}
static void TestFilterMode(void)
{
Mac::Filter filter;
Mac::ExtAddress addr = MakeExtAddress(1);
int8_t rss;
printf("TestFilterMode\n");
// Verify initial mode and transitions between modes.
VerifyOrQuit(filter.GetMode() == Mac::Filter::kModeRssInOnly);
filter.SetMode(Mac::Filter::kModeAllowlist);
VerifyOrQuit(filter.GetMode() == Mac::Filter::kModeAllowlist);
filter.SetMode(Mac::Filter::kModeDenylist);
VerifyOrQuit(filter.GetMode() == Mac::Filter::kModeDenylist);
filter.SetMode(Mac::Filter::kModeRssInOnly);
VerifyOrQuit(filter.GetMode() == Mac::Filter::kModeRssInOnly);
// Verify Apply() behavior when filter list is empty.
SuccessOrQuit(filter.Apply(addr, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
filter.SetMode(Mac::Filter::kModeAllowlist);
VerifyOrQuit(filter.Apply(addr, rss) == kErrorAddressFiltered);
filter.SetMode(Mac::Filter::kModeDenylist);
SuccessOrQuit(filter.Apply(addr, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
}
static void TestAddressAllowlist(void)
{
Mac::Filter filter;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
Mac::ExtAddress addr3 = MakeExtAddress(3);
int8_t rss;
printf("TestAddressAllowlist\n");
filter.SetMode(Mac::Filter::kModeAllowlist);
// Add addresses and verify allowlist filtering.
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.AddAddress(addr2));
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
SuccessOrQuit(filter.Apply(addr2, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
VerifyOrQuit(filter.Apply(addr3, rss) == kErrorAddressFiltered);
// Adding duplicate address should succeed and keep entry in allowlist.
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.Apply(addr1, rss));
// Remove an address and verify it is filtered.
filter.RemoveAddress(addr1);
VerifyOrQuit(filter.Apply(addr1, rss) == kErrorAddressFiltered);
SuccessOrQuit(filter.Apply(addr2, rss));
// Removing non-existent address should not affect existing entries.
filter.RemoveAddress(addr3);
SuccessOrQuit(filter.Apply(addr2, rss));
// Clear all addresses and verify all are filtered.
filter.ClearAddresses();
VerifyOrQuit(filter.Apply(addr2, rss) == kErrorAddressFiltered);
}
static void TestAddressDenylist(void)
{
Mac::Filter filter;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
Mac::ExtAddress addr3 = MakeExtAddress(3);
int8_t rss;
printf("TestAddressDenylist\n");
filter.SetMode(Mac::Filter::kModeDenylist);
// Add addresses and verify denylist filtering.
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.AddAddress(addr2));
VerifyOrQuit(filter.Apply(addr1, rss) == kErrorAddressFiltered);
VerifyOrQuit(filter.Apply(addr2, rss) == kErrorAddressFiltered);
SuccessOrQuit(filter.Apply(addr3, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
// Remove an address from denylist and verify it is allowed.
filter.RemoveAddress(addr1);
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(filter.Apply(addr2, rss) == kErrorAddressFiltered);
// Removing non-existent address should not affect existing entries.
filter.RemoveAddress(addr3);
VerifyOrQuit(filter.Apply(addr2, rss) == kErrorAddressFiltered);
// Clear all addresses and verify all are allowed.
filter.ClearAddresses();
SuccessOrQuit(filter.Apply(addr2, rss));
}
static void TestAddressIteration(void)
{
Mac::Filter filter;
Mac::Filter::Iterator iter;
Mac::Filter::EntryInfo info;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
Mac::ExtAddress addr3 = MakeExtAddress(3);
uint16_t count;
bool foundAddr1;
bool foundAddr2;
bool foundAddr3;
printf("TestAddressIteration\n");
// Empty filter should return kErrorNotFound.
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
// Add multiple addresses and iterate through all entries.
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.AddAddress(addr2));
SuccessOrQuit(filter.AddAddress(addr3));
iter = Mac::Filter::kIteratorInit;
count = 0;
foundAddr1 = false;
foundAddr2 = false;
foundAddr3 = false;
while (filter.GetNextAddress(iter, info) == kErrorNone)
{
count++;
VerifyOrQuit(info.mRssIn == Mac::Filter::kFixedRssDisabled);
if (AsCoreType(&info.mExtAddress) == addr1)
{
foundAddr1 = true;
}
else if (AsCoreType(&info.mExtAddress) == addr2)
{
foundAddr2 = true;
}
else if (AsCoreType(&info.mExtAddress) == addr3)
{
foundAddr3 = true;
}
}
VerifyOrQuit(count == 3);
VerifyOrQuit(foundAddr1 && foundAddr2 && foundAddr3);
// Remove middle entry and verify remaining entries iterate correctly.
filter.RemoveAddress(addr2);
iter = Mac::Filter::kIteratorInit;
count = 0;
foundAddr1 = false;
foundAddr2 = false;
foundAddr3 = false;
while (filter.GetNextAddress(iter, info) == kErrorNone)
{
count++;
if (AsCoreType(&info.mExtAddress) == addr1)
{
foundAddr1 = true;
}
else if (AsCoreType(&info.mExtAddress) == addr2)
{
foundAddr2 = true;
}
else if (AsCoreType(&info.mExtAddress) == addr3)
{
foundAddr3 = true;
}
}
VerifyOrQuit(count == 2);
VerifyOrQuit(foundAddr1 && !foundAddr2 && foundAddr3);
// Clear all addresses and verify iteration returns kErrorNotFound.
filter.ClearAddresses();
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
}
static void TestRssInFiltering(void)
{
Mac::Filter filter;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
int8_t rss;
printf("TestRssInFiltering\n");
// Verify kFixedRssDisabled cannot be added as valid RSS.
VerifyOrQuit(filter.AddRssIn(addr1, Mac::Filter::kFixedRssDisabled) == kErrorInvalidArgs);
// Add fixed RSS and verify retrieval.
SuccessOrQuit(filter.AddRssIn(addr1, -70));
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == -70);
SuccessOrQuit(filter.Apply(addr2, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
// Update fixed RSS for existing entry.
SuccessOrQuit(filter.AddRssIn(addr1, -50));
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == -50);
// Remove fixed RSS and verify default is restored.
filter.RemoveRssIn(addr1);
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
// Removing non-existent RSS entry should not cause issues.
filter.RemoveRssIn(addr2);
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
}
static void TestDefaultRssIn(void)
{
Mac::Filter filter;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
int8_t rss;
printf("TestDefaultRssIn\n");
// Set default RSS and verify it applies to all addresses.
filter.SetDefaultRssIn(-65);
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == -65);
SuccessOrQuit(filter.Apply(addr2, rss));
VerifyOrQuit(rss == -65);
// Explicit RSS should override default RSS.
SuccessOrQuit(filter.AddRssIn(addr1, -40));
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == -40);
SuccessOrQuit(filter.Apply(addr2, rss));
VerifyOrQuit(rss == -65);
// Removing explicit RSS falls back to default RSS.
filter.RemoveRssIn(addr1);
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == -65);
// Clear default RSS and verify no fixed RSS is applied.
filter.ClearDefaultRssIn();
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
// ClearAllRssIn() clears both explicit and default RSS.
filter.SetDefaultRssIn(-80);
SuccessOrQuit(filter.AddRssIn(addr1, -30));
SuccessOrQuit(filter.AddRssIn(addr2, -45));
filter.ClearAllRssIn();
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
SuccessOrQuit(filter.Apply(addr2, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
}
static void TestRssInIteration(void)
{
Mac::Filter filter;
Mac::Filter::Iterator iter;
Mac::Filter::EntryInfo info;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
Mac::ExtAddress defaultRssAddr;
uint16_t count;
bool foundAddr1;
bool foundAddr2;
bool foundDefault;
printf("TestRssInIteration\n");
defaultRssAddr.Fill(0xff);
// Empty filter with no default RSS yields nothing.
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
// Empty filter with default RSS yields synthetic all-0xff entry.
filter.SetDefaultRssIn(-75);
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextRssIn(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == defaultRssAddr);
VerifyOrQuit(info.mRssIn == -75);
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
// Explicit entries are yielded along with default RSS.
SuccessOrQuit(filter.AddRssIn(addr1, -60));
SuccessOrQuit(filter.AddRssIn(addr2, -80));
iter = Mac::Filter::kIteratorInit;
count = 0;
foundAddr1 = false;
foundAddr2 = false;
foundDefault = false;
while (filter.GetNextRssIn(iter, info) == kErrorNone)
{
count++;
if (AsCoreType(&info.mExtAddress) == addr1)
{
foundAddr1 = true;
VerifyOrQuit(info.mRssIn == -60);
}
else if (AsCoreType(&info.mExtAddress) == addr2)
{
foundAddr2 = true;
VerifyOrQuit(info.mRssIn == -80);
}
else if (AsCoreType(&info.mExtAddress) == defaultRssAddr)
{
foundDefault = true;
VerifyOrQuit(info.mRssIn == -75);
}
}
VerifyOrQuit(count == 3);
VerifyOrQuit(foundAddr1 && foundAddr2 && foundDefault);
// Clear default RSS and verify only explicit entries are yielded.
filter.ClearDefaultRssIn();
iter = Mac::Filter::kIteratorInit;
count = 0;
foundAddr1 = false;
foundAddr2 = false;
foundDefault = false;
while (filter.GetNextRssIn(iter, info) == kErrorNone)
{
count++;
if (AsCoreType(&info.mExtAddress) == addr1)
{
foundAddr1 = true;
VerifyOrQuit(info.mRssIn == -60);
}
else if (AsCoreType(&info.mExtAddress) == addr2)
{
foundAddr2 = true;
VerifyOrQuit(info.mRssIn == -80);
}
else if (AsCoreType(&info.mExtAddress) == defaultRssAddr)
{
foundDefault = true;
}
}
VerifyOrQuit(count == 2);
VerifyOrQuit(foundAddr1 && foundAddr2 && !foundDefault);
}
static void TestAddressAndRssCoexistence(void)
{
Mac::Filter filter;
Mac::Filter::Iterator iter;
Mac::Filter::EntryInfo info;
Mac::ExtAddress addr1 = MakeExtAddress(1);
Mac::ExtAddress addr2 = MakeExtAddress(2);
int8_t rss;
printf("TestAddressAndRssCoexistence\n");
filter.SetMode(Mac::Filter::kModeAllowlist);
// Add both address and RSS-In to the same entry.
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.AddRssIn(addr1, -55));
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == -55);
// Verify entry is present in both address and RSS iterations.
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextAddress(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == addr1);
VerifyOrQuit(info.mRssIn == -55);
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextRssIn(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == addr1);
VerifyOrQuit(info.mRssIn == -55);
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
// Remove address filter only; RSS-In entry must persist.
filter.RemoveAddress(addr1);
VerifyOrQuit(filter.Apply(addr1, rss) == kErrorAddressFiltered);
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextRssIn(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == addr1);
VerifyOrQuit(info.mRssIn == -55);
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
// Re-add address and remove RSS-In; address filter must persist.
SuccessOrQuit(filter.AddAddress(addr1));
filter.RemoveRssIn(addr1);
SuccessOrQuit(filter.Apply(addr1, rss));
VerifyOrQuit(rss == Mac::Filter::kFixedRssDisabled);
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextAddress(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == addr1);
VerifyOrQuit(info.mRssIn == Mac::Filter::kFixedRssDisabled);
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
// Remove address; entry should be completely removed.
filter.RemoveAddress(addr1);
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
// ClearAddresses() removes address filters while preserving RSS-In entries.
filter.ClearAllRssIn();
filter.ClearAddresses();
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.AddRssIn(addr1, -48));
SuccessOrQuit(filter.AddAddress(addr2));
filter.ClearAddresses();
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextRssIn(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == addr1);
VerifyOrQuit(info.mRssIn == -48);
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
// ClearAllRssIn() removes RSS-In entries while preserving address filters.
filter.ClearAllRssIn();
filter.ClearAddresses();
SuccessOrQuit(filter.AddAddress(addr1));
SuccessOrQuit(filter.AddRssIn(addr1, -48));
SuccessOrQuit(filter.AddRssIn(addr2, -62));
filter.ClearAllRssIn();
iter = Mac::Filter::kIteratorInit;
VerifyOrQuit(filter.GetNextRssIn(iter, info) == kErrorNotFound);
iter = Mac::Filter::kIteratorInit;
SuccessOrQuit(filter.GetNextAddress(iter, info));
VerifyOrQuit(AsCoreType(&info.mExtAddress) == addr1);
VerifyOrQuit(info.mRssIn == Mac::Filter::kFixedRssDisabled);
VerifyOrQuit(filter.GetNextAddress(iter, info) == kErrorNotFound);
}
static void TestCapacityLimits(void)
{
constexpr uint16_t kMaxEntries = OPENTHREAD_CONFIG_MAC_FILTER_SIZE;
Mac::Filter filter;
Mac::Filter::Iterator iter;
Mac::Filter::EntryInfo info;
Mac::ExtAddress extraAddr = MakeExtAddress(kMaxEntries + 1);
uint16_t count;
uint16_t i;
printf("TestCapacityLimits\n");
// Fill filter to maximum capacity.
for (i = 1; i <= kMaxEntries; i++)
{
SuccessOrQuit(filter.AddAddress(MakeExtAddress(i)));
}
// Adding beyond capacity fails with kErrorNoBufs.
VerifyOrQuit(filter.AddAddress(extraAddr) == kErrorNoBufs);
// Adding RSS-In to an existing address entry reuses slot.
SuccessOrQuit(filter.AddRssIn(MakeExtAddress(1), -60));
// Adding RSS-In for a new address fails when table is full.
VerifyOrQuit(filter.AddRssIn(extraAddr, -60) == kErrorNoBufs);
// Free slot by removing an address-only entry.
filter.RemoveAddress(MakeExtAddress(2));
SuccessOrQuit(filter.AddAddress(extraAddr));
VerifyOrQuit(filter.AddAddress(MakeExtAddress(kMaxEntries + 2)) == kErrorNoBufs);
// Removing address from entry with active RSS-In does not free slot.
filter.RemoveAddress(MakeExtAddress(1));
VerifyOrQuit(filter.AddAddress(MakeExtAddress(kMaxEntries + 2)) == kErrorNoBufs);
// Removing RSS-In now frees the slot.
filter.RemoveRssIn(MakeExtAddress(1));
SuccessOrQuit(filter.AddAddress(MakeExtAddress(kMaxEntries + 2)));
// Clear all entries and verify all slots are freed.
filter.ClearAddresses();
filter.ClearAllRssIn();
count = 0;
iter = Mac::Filter::kIteratorInit;
while (filter.GetNextAddress(iter, info) == kErrorNone)
{
count++;
}
VerifyOrQuit(count == 0);
iter = Mac::Filter::kIteratorInit;
while (filter.GetNextRssIn(iter, info) == kErrorNone)
{
count++;
}
VerifyOrQuit(count == 0);
// Verify all slots can be populated again up to capacity.
for (i = 1; i <= kMaxEntries; i++)
{
SuccessOrQuit(filter.AddAddress(MakeExtAddress(i)));
}
VerifyOrQuit(filter.AddAddress(extraAddr) == kErrorNoBufs);
}
#endif // OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
} // namespace ot
int main(void)
{
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
ot::TestFilterMode();
ot::TestAddressAllowlist();
ot::TestAddressDenylist();
ot::TestAddressIteration();
ot::TestRssInFiltering();
ot::TestDefaultRssIn();
ot::TestRssInIteration();
ot::TestAddressAndRssCoexistence();
ot::TestCapacityLimits();
printf("All tests passed\n");
#else
printf("MAC filter is not enabled\n");
#endif
return 0;
}