[bbr] simplify MulticastListenersTable implementation (#13036)

This commit simplifies the `MulticastListenersTable` by replacing the
custom heap-based sorting logic with a standard `Array` and integrating
an internal `TimerMilli` (`mTimer`) to handle entry expirations.

Previously, the table maintained a min-heap based on expiration times
(`FixHeap`, `SiftHeapElemDown`, `SiftHeapElemUp`) and required
external calls to `Expire()` every second. The new implementation
uses `mListeners.FindMatching()` and `mListeners.RemoveAllMatching()`,
significantly reducing code complexity and maintenance overhead.

The unit tests in `test_multicast_listeners_table.cpp` are also updated
to reflect the simplified model
This commit is contained in:
Abtin Keshavarzian
2026-05-05 07:46:31 -07:00
committed by GitHub
parent 4c12431b68
commit 3f82c1dfa5
4 changed files with 142 additions and 390 deletions
-4
View File
@@ -110,10 +110,6 @@ void Manager::HandleNotifierEvents(Events aEvents)
void Manager::HandleTimer(void)
{
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
mMulticastListenersTable.Expire();
#endif
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
mNdProxyTable.HandleTimer();
#endif
@@ -43,108 +43,95 @@ namespace BackboneRouter {
RegisterLogModule("BbrMlt");
MulticastListenersTable::MulticastListenersTable(Instance &aInstance)
: InstanceLocator(aInstance)
, mTimer(aInstance)
{
}
Error MulticastListenersTable::Add(const Ip6::Address &aAddress, Time aExpireTime)
{
Error error = kErrorNone;
Error error = kErrorNone;
bool isNew = false;
Listener *entry;
VerifyOrExit(aAddress.IsMulticastLargerThanRealmLocal(), error = kErrorInvalidArgs);
for (uint16_t i = 0; i < mNumValidListeners; i++)
{
Listener &listener = mListeners[i];
entry = mListeners.FindMatching(aAddress);
if (listener.GetAddress() == aAddress)
{
listener.SetExpireTime(aExpireTime);
FixHeap(i);
ExitNow();
}
if (entry == nullptr)
{
entry = mListeners.PushBack();
isNew = true;
}
VerifyOrExit(mNumValidListeners < GetArrayLength(mListeners), error = kErrorNoBufs);
VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
mListeners[mNumValidListeners].SetAddress(aAddress);
mListeners[mNumValidListeners].SetExpireTime(aExpireTime);
mNumValidListeners++;
entry->mAddress = aAddress;
entry->mExpireTime = aExpireTime;
FixHeap(mNumValidListeners - 1);
mTimer.FireAtIfEarlier(aExpireTime);
mCallback.InvokeIfSet(MapEnum(Listener::kEventAdded), &aAddress);
if (isNew)
{
InvokeCallback(kEventAdded, aAddress);
}
exit:
Log(kAdd, aAddress, aExpireTime, error);
CheckInvariants();
return error;
}
void MulticastListenersTable::Remove(const Ip6::Address &aAddress)
{
Error error = kErrorNotFound;
Error error = kErrorNone;
Listener *entry;
for (uint16_t i = 0; i < mNumValidListeners; i++)
{
Listener &listener = mListeners[i];
entry = mListeners.FindMatching(aAddress);
VerifyOrExit(entry != nullptr, error = kErrorNotFound);
if (listener.GetAddress() == aAddress)
{
mNumValidListeners--;
mListeners.Remove(*entry);
if (i != mNumValidListeners)
{
listener = mListeners[mNumValidListeners];
FixHeap(i);
}
mCallback.InvokeIfSet(MapEnum(Listener::kEventRemoved), &aAddress);
ExitNow(error = kErrorNone);
}
}
InvokeCallback(kEventRemoved, aAddress);
exit:
Log(kRemove, aAddress, TimeMilli(0), error);
CheckInvariants();
}
void MulticastListenersTable::Expire(void)
void MulticastListenersTable::HandleTimer(void)
{
TimeMilli now = TimerMilli::GetNow();
Ip6::Address address;
NextFireTime nextTime(now);
while (mNumValidListeners > 0 && now >= mListeners[0].GetExpireTime())
for (uint16_t i = 0; i < mListeners.GetLength();)
{
Log(kExpire, mListeners[0].GetAddress(), mListeners[0].GetExpireTime(), kErrorNone);
address = mListeners[0].GetAddress();
mNumValidListeners--;
if (mNumValidListeners > 0)
if (mListeners[i].mExpireTime <= now)
{
mListeners[0] = mListeners[mNumValidListeners];
FixHeap(0);
}
Ip6::Address address = mListeners[i].mAddress;
mCallback.InvokeIfSet(MapEnum(Listener::kEventRemoved), &address);
Log(kExpire, address, mListeners[i].mExpireTime, kErrorNone);
mListeners.Remove(mListeners[i]);
InvokeCallback(kEventRemoved, address);
// When the entry is removed from the array it is replaced
// with the last element. In this case, we do not
// increment `i`.
}
else
{
nextTime.UpdateIfEarlier(mListeners[i].mExpireTime);
i++;
}
}
CheckInvariants();
mTimer.FireAtIfEarlier(nextTime);
}
bool MulticastListenersTable::Has(const Ip6::Address &aAddress) const
bool MulticastListenersTable::Has(const Ip6::Address &aAddress) const { return mListeners.ContainsMatching(aAddress); }
void MulticastListenersTable::InvokeCallback(Event aEvent, const Ip6::Address &aAddress) const
{
bool has = false;
for (uint16_t i = 0; i < mNumValidListeners; i++)
{
if (mListeners[i].GetAddress() == aAddress)
{
has = true;
ExitNow();
}
}
exit:
return has;
mCallback.InvokeIfSet(aEvent, &aAddress);
}
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG)
@@ -167,146 +154,43 @@ void MulticastListenersTable::Log(Action aAction,
void MulticastListenersTable::Log(Action, const Ip6::Address &, TimeMilli, Error) const {}
#endif
void MulticastListenersTable::FixHeap(uint16_t aIndex)
{
if (!SiftHeapElemDown(aIndex))
{
SiftHeapElemUp(aIndex);
}
}
void MulticastListenersTable::CheckInvariants(void) const
{
#if OPENTHREAD_EXAMPLES_SIMULATION && OPENTHREAD_CONFIG_ASSERT_ENABLE
for (uint16_t child = 1; child < mNumValidListeners; ++child)
{
uint16_t parent = (child - 1) / 2;
OT_ASSERT(!(mListeners[child] < mListeners[parent]));
}
#endif
}
bool MulticastListenersTable::SiftHeapElemDown(uint16_t aIndex)
{
uint16_t index = aIndex;
Listener saveElem;
OT_ASSERT(aIndex < mNumValidListeners);
saveElem = mListeners[aIndex];
for (;;)
{
uint16_t child = 2 * index + 1;
if (child >= mNumValidListeners || child <= index) // child <= index after int overflow
{
break;
}
if (child + 1 < mNumValidListeners && mListeners[child + 1] < mListeners[child])
{
child++;
}
if (!(mListeners[child] < saveElem))
{
break;
}
mListeners[index] = mListeners[child];
index = child;
}
if (index > aIndex)
{
mListeners[index] = saveElem;
}
return index > aIndex;
}
void MulticastListenersTable::SiftHeapElemUp(uint16_t aIndex)
{
uint16_t index = aIndex;
Listener saveElem;
OT_ASSERT(aIndex < mNumValidListeners);
saveElem = mListeners[aIndex];
for (;;)
{
uint16_t parent = (index - 1) / 2;
if (index == 0 || !(saveElem < mListeners[parent]))
{
break;
}
mListeners[index] = mListeners[parent];
index = parent;
}
if (index < aIndex)
{
mListeners[index] = saveElem;
}
}
MulticastListenersTable::Listener *MulticastListenersTable::IteratorBuilder::begin(void)
{
return &Get<MulticastListenersTable>().mListeners[0];
}
MulticastListenersTable::Listener *MulticastListenersTable::IteratorBuilder::end(void)
{
return &Get<MulticastListenersTable>().mListeners[Get<MulticastListenersTable>().mNumValidListeners];
}
void MulticastListenersTable::Clear(void)
{
if (mCallback.IsSet())
while (!mListeners.IsEmpty())
{
for (uint16_t i = 0; i < mNumValidListeners; i++)
{
mCallback.Invoke(MapEnum(Listener::kEventRemoved), &mListeners[i].GetAddress());
}
Ip6::Address address = mListeners.Back()->mAddress;
mListeners.PopBack();
InvokeCallback(kEventRemoved, address);
}
mNumValidListeners = 0;
CheckInvariants();
mTimer.Stop();
}
void MulticastListenersTable::SetCallback(Listener::Callback aCallback, void *aContext)
void MulticastListenersTable::SetCallback(ListenerCallback aCallback, void *aContext)
{
mCallback.Set(aCallback, aContext);
if (mCallback.IsSet())
{
for (uint16_t i = 0; i < mNumValidListeners; i++)
for (const Listener &listener : mListeners)
{
mCallback.Invoke(MapEnum(Listener::kEventAdded), &mListeners[i].GetAddress());
InvokeCallback(kEventAdded, listener.mAddress);
}
}
}
Error MulticastListenersTable::GetNext(Listener::Iterator &aIterator, Listener::Info &aInfo)
Error MulticastListenersTable::GetNext(ListenerIterator &aIterator, ListenerInfo &aInfo)
{
Error error = kErrorNone;
TimeMilli now;
VerifyOrExit(aIterator < mNumValidListeners, error = kErrorNotFound);
VerifyOrExit(aIterator < mListeners.GetLength(), error = kErrorNotFound);
now = TimerMilli::GetNow();
aInfo.mAddress = mListeners[aIterator].mAddress;
aInfo.mTimeout =
Time::MsecToSec(mListeners[aIterator].mExpireTime > now ? mListeners[aIterator].mExpireTime - now : 0);
aInfo.mTimeout = Time::MsecToSec(mListeners[aIterator].mExpireTime.DetermineRemainingDurationFrom(now));
aIterator++;
@@ -40,11 +40,13 @@
#include <openthread/backbone_router_ftd.h>
#include "common/array.hpp"
#include "common/as_core_type.hpp"
#include "common/callback.hpp"
#include "common/non_copyable.hpp"
#include "common/notifier.hpp"
#include "common/time.hpp"
#include "common/timer.hpp"
#include "net/ip6_address.hpp"
namespace ot {
@@ -56,66 +58,17 @@ namespace BackboneRouter {
*/
class MulticastListenersTable : public InstanceLocator, private NonCopyable
{
class IteratorBuilder;
public:
/**
* Represents a Multicast Listener entry.
*/
class Listener : public Clearable<Listener>
{
friend class MulticastListenersTable;
public:
typedef otBackboneRouterMulticastListenerCallback Callback; ///< Listener callback.
typedef otBackboneRouterMulticastListenerIterator Iterator; ///< Iterator to go over Listener entries.
typedef otBackboneRouterMulticastListenerInfo Info; ///< Listener info.
enum Event : uint8_t ///< Listener Event
{
kEventAdded = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED, ///< Listener was added.
kEventRemoved = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED, ///< Listener was removed.
};
/**
* Initializes the `Listener` object.
*/
Listener(void) { Clear(); }
/**
* Returns the Multicast Listener address.
*
* @returns The Multicast Listener address.
*/
const Ip6::Address &GetAddress(void) const { return mAddress; }
/**
* Returns the expire time of the Multicast Listener.
*
* @returns The Multicast Listener expire time.
*/
const TimeMilli GetExpireTime(void) const { return mExpireTime; }
private:
void SetAddress(const Ip6::Address &aAddress) { mAddress = aAddress; }
void SetExpireTime(TimeMilli aExpireTime) { mExpireTime = aExpireTime; }
bool operator<(const Listener &aOther) const { return GetExpireTime() < aOther.GetExpireTime(); }
Ip6::Address mAddress;
TimeMilli mExpireTime;
};
typedef otBackboneRouterMulticastListenerCallback ListenerCallback; ///< Listener Callback.
typedef otBackboneRouterMulticastListenerInfo ListenerInfo; ///< Listener info.
typedef otBackboneRouterMulticastListenerIterator ListenerIterator; ///< Iterator to go over entries.
/**
* Initializes the Multicast Listeners Table.
*
* @param[in] aInstance A reference to the OpenThread instance.
*/
explicit MulticastListenersTable(Instance &aInstance)
: InstanceLocator(aInstance)
, mNumValidListeners(0)
{
}
explicit MulticastListenersTable(Instance &aInstance);
/**
* Adds a Multicast Listener with given address and expire time.
@@ -136,17 +89,12 @@ public:
*/
void Remove(const Ip6::Address &aAddress);
/**
* Removes expired Multicast Listeners.
*/
void Expire(void);
/**
* Counts the number of valid Multicast Listeners.
*
* @returns The number of valid Multicast Listeners.
*/
uint16_t Count(void) const { return mNumValidListeners; }
uint16_t Count(void) const { return mListeners.GetLength(); }
/**
* Indicates whether or not the Multicast Listeners Table contains the given address.
@@ -158,17 +106,6 @@ public:
*/
bool Has(const Ip6::Address &aAddress) const;
/**
* Enables range-based `for` loop iteration over all Multicast Listeners.
*
* Should be used as follows:
*
* for (MulticastListenersTable::Listener &listener : Get<MulticastListenersTable>().Iterate())
*
* @returns An IteratorBuilder instance.
*/
IteratorBuilder Iterate(void) { return IteratorBuilder(GetInstance()); }
/**
* Removes all the Multicast Listeners.
*/
@@ -180,7 +117,7 @@ public:
* @param[in] aCallback The callback function.
* @param[in] aContext A user context pointer.
*/
void SetCallback(Listener::Callback aCallback, void *aContext);
void SetCallback(ListenerCallback aCallback, void *aContext);
/**
* Gets the next Multicast Listener.
@@ -191,24 +128,17 @@ public:
* @retval kErrorNone Successfully found the next Multicast Listener info.
* @retval kErrorNotFound No subsequent Multicast Listener was found.
*/
Error GetNext(Listener::Iterator &aIterator, Listener::Info &aInfo);
Error GetNext(ListenerIterator &aIterator, ListenerInfo &aInfo);
private:
static constexpr uint16_t kTableSize = OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS;
static_assert(kTableSize >= 75, "Thread 1.2 Conformance requires table size of at least 75 listeners.");
class IteratorBuilder : InstanceLocator
{
public:
explicit IteratorBuilder(Instance &aInstance)
: InstanceLocator(aInstance)
{
}
typedef otBackboneRouterMulticastListenerEvent Event;
Listener *begin(void);
Listener *end(void);
};
static constexpr Event kEventAdded = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED;
static constexpr Event kEventRemoved = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED;
enum Action : uint8_t
{
@@ -217,22 +147,28 @@ private:
kExpire,
};
struct Listener
{
bool Matches(const Ip6::Address &aAddress) const { return mAddress == aAddress; }
Ip6::Address mAddress;
TimeMilli mExpireTime;
};
void InvokeCallback(Event aEvent, const Ip6::Address &aAddress) const;
void HandleTimer(void);
void Log(Action aAction, const Ip6::Address &aAddress, TimeMilli aExpireTime, Error aError) const;
void FixHeap(uint16_t aIndex);
bool SiftHeapElemDown(uint16_t aIndex);
void SiftHeapElemUp(uint16_t aIndex);
void CheckInvariants(void) const;
using ListenerArray = Array<Listener, kTableSize, uint16_t>;
using ExpireTimer = TimerMilliIn<MulticastListenersTable, &MulticastListenersTable::HandleTimer>;
Listener mListeners[kTableSize];
uint16_t mNumValidListeners;
Callback<Listener::Callback> mCallback;
ListenerArray mListeners;
ExpireTimer mTimer;
Callback<ListenerCallback> mCallback;
};
} // namespace BackboneRouter
DefineMapEnum(otBackboneRouterMulticastListenerEvent, BackboneRouter::MulticastListenersTable::Listener::Event);
/**
* @}
*/
+52 -116
View File
@@ -42,150 +42,86 @@
namespace ot {
static Instance *sInstance;
using namespace ot::BackboneRouter;
static const otIp6Address MA201 = {
{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}};
static const otIp6Address MA301 = {
{{0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}};
static const otIp6Address MA401 = {
{{0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}};
static const otIp6Address MA501 = {
{{0xff, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}};
uint32_t sNow;
extern "C" uint32_t otPlatAlarmMilliGetNow(void) { return sNow; }
void testMulticastListenersTableAPIs(Instance *aInstance);
void TestMulticastListenersTable(void)
{
sInstance = testInitInstance();
VerifyOrQuit(sInstance != nullptr);
static constexpr uint16_t kMaxSize = OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS;
MulticastListenersTable &table = sInstance->Get<MulticastListenersTable>();
MulticastListenersTable *table;
Instance *instance;
Ip6::Address kMa201;
Ip6::Address kMa301;
Ip6::Address kMa401;
Ip6::Address kMa501;
TimeMilli now(0);
for (MulticastListenersTable::Listener &listener : table.Iterate())
{
VerifyOrQuit(false, "MulticastListenersTable should be empty when created");
}
SuccessOrQuit(kMa201.FromString("ff02::01"));
SuccessOrQuit(kMa301.FromString("ff03::01"));
SuccessOrQuit(kMa401.FromString("ff04::01"));
SuccessOrQuit(kMa501.FromString("ff05::01"));
// Removing from empty table should be OK
table.Remove(static_cast<const Ip6::Address &>(MA401));
instance = testInitInstance();
VerifyOrQuit(instance != nullptr);
sNow = 1;
// Add valid MAs should succeed
SuccessOrQuit(table.Add(static_cast<const Ip6::Address &>(MA401), TimerMilli::GetNow()));
SuccessOrQuit(table.Add(static_cast<const Ip6::Address &>(MA501), TimerMilli::GetNow()));
VerifyOrQuit(table.Count() == 2, "Table count is wrong");
table = &instance->Get<MulticastListenersTable>();
VerifyOrQuit(table->Count() == 0);
table->Remove(kMa201);
VerifyOrQuit(table->Count() == 0);
SuccessOrQuit(table->Add(kMa401, now));
SuccessOrQuit(table->Add(kMa501, now));
VerifyOrQuit(table->Count() == 2);
// Add invalid MAs should fail with kErrorInvalidArgs
VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA201), TimerMilli::GetNow()) == kErrorInvalidArgs,
"failed to detect bad arg");
VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA301), TimerMilli::GetNow()) == kErrorInvalidArgs,
"failed to detect bad arg");
VerifyOrQuit(table->Add(kMa201, now) == kErrorInvalidArgs);
VerifyOrQuit(table->Add(kMa301, now) == kErrorInvalidArgs);
VerifyOrQuit(table->Count() == 2);
// Expire should expire outdated Listeners
sNow = 2;
table.Expire();
VerifyOrQuit(table.Count() == 0, "Table count is wrong");
// Re-add an existing entry
SuccessOrQuit(table->Add(kMa501, now));
VerifyOrQuit(table->Count() == 2);
// Add different addresses until the table is full
sNow = 10;
for (uint16_t i = 0; i < OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS; i++)
VerifyOrQuit(table->Has(kMa401));
VerifyOrQuit(table->Has(kMa501));
VerifyOrQuit(!table->Has(kMa201));
VerifyOrQuit(!table->Has(kMa301));
table->Clear();
VerifyOrQuit(table->Count() == 0);
for (uint16_t i = 0; i < kMaxSize; i++)
{
Ip6::Address address;
address = static_cast<const Ip6::Address &>(MA401);
address = kMa401;
address.mFields.m16[7] = BigEndian::HostSwap16(i);
SuccessOrQuit(table.Add(address, TimerMilli::GetNow() + i));
VerifyOrQuit(table.Count() == i + 1, "Table count is wrong");
SuccessOrQuit(table->Add(address, now));
VerifyOrQuit(table->Count() == i + 1);
VerifyOrQuit(table->Has(address));
}
// Now the table is full, we can't add more addresses
VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA501), TimerMilli::GetNow()) == kErrorNoBufs,
"succeeded when table is full");
// Now the table is full, adding more entries should fail
VerifyOrQuit(table->Add(kMa501, now) == kErrorNoBufs);
// Expire one Listener at a time
for (uint16_t i = 0; i < OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS; i++)
{
table.Expire();
VerifyOrQuit(table.Count() == OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS - i - 1, "Table count is wrong");
VerifyOrQuit(table->Count() == kMaxSize);
sNow += 1;
}
// Now the table should be empty
VerifyOrQuit(table.Count() == 0, "Table count is wrong");
// Now test the APIs
testMulticastListenersTableAPIs(sInstance);
// Do some fuzzy test
for (uint16_t i = 0; i < 10000; i++)
for (uint16_t i = 0; i < kMaxSize; i++)
{
Ip6::Address address;
sNow += 10;
table.Expire();
address = kMa401;
address.mFields.m16[7] = BigEndian::HostSwap16(i);
for (MulticastListenersTable::Listener &listener : table.Iterate())
{
OT_ASSERT(listener.GetAddress().IsMulticastLargerThanRealmLocal());
OT_ASSERT(listener.GetExpireTime() > TimerMilli::GetNow());
}
address = static_cast<const Ip6::Address &>(MA401);
address.mFields.m16[7] = Random::NonCrypto::GetUint16InRange(1, 1000);
IgnoreError(table.Add(address, TimerMilli::GetNow() + Random::NonCrypto::GetUint32InRange(1, 100)));
address.mFields.m16[7] = Random::NonCrypto::GetUint16InRange(1, 1000);
if (Random::NonCrypto::GetUint16InRange(0, 2) == 0)
{
table.Remove(address);
}
VerifyOrQuit(table->Has(address));
}
}
void testMulticastListenersTableAPIs(Instance *aInstance)
{
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
otBackboneRouterMulticastListenerInfo info;
size_t table_size = 0;
while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == kErrorNone)
{
VerifyOrQuit(false, "Table should be empty");
}
SuccessOrQuit(otBackboneRouterMulticastListenerAdd(aInstance, &MA401, 30));
table_size = 0, iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == kErrorNone)
{
table_size++;
VerifyOrQuit(memcmp(&info.mAddress, &MA401, sizeof(otIp6Address)) == 0, "bad address");
VerifyOrQuit(info.mTimeout == 30, "bad timeout");
}
VerifyOrQuit(table_size == 1, "Table size is wrong");
otBackboneRouterMulticastListenerClear(aInstance);
iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == kErrorNone)
{
VerifyOrQuit(false, "Table should be empty");
}
#endif
}
} // namespace ot
int main(void)