blob: 8141c760a17cf5acd6b74c2f6444e07c14d23fc9 [file] [log] [blame]
/*
* Copyright (c) 2021, 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 the Network Data Publisher.
*
*/
#include "network_data_publisher.hpp"
#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE
#include "common/array.hpp"
#include "common/code_utils.hpp"
#include "common/const_cast.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "common/random.hpp"
#include "thread/network_data_local.hpp"
#include "thread/network_data_service.hpp"
namespace ot {
namespace NetworkData {
RegisterLogModule("NetDataPublshr");
//---------------------------------------------------------------------------------------------------------------------
// Publisher
Publisher::Publisher(Instance &aInstance)
: InstanceLocator(aInstance)
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
, mDnsSrpServiceEntry(aInstance)
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
, mPrefixCallback(nullptr)
, mPrefixCallbackContext(nullptr)
#endif
, mTimer(aInstance, Publisher::HandleTimer)
{
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
// Since the `PrefixEntry` type is used in an array,
// we cannot use a constructor with an argument (e.g.,
// we cannot use `InstacneLocator`) so we use
// `IntanceLocatorInit` and `Init()` the entries one
// by one.
for (PrefixEntry &entry : mPrefixEntries)
{
entry.Init(aInstance);
}
#endif
}
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
void Publisher::SetPrefixCallback(PrefixCallback aCallback, void *aContext)
{
mPrefixCallback = aCallback;
mPrefixCallbackContext = aContext;
}
Error Publisher::PublishOnMeshPrefix(const OnMeshPrefixConfig &aConfig)
{
Error error = kErrorNone;
PrefixEntry *entry;
VerifyOrExit(aConfig.IsValid(GetInstance()), error = kErrorInvalidArgs);
VerifyOrExit(aConfig.mStable, error = kErrorInvalidArgs);
entry = FindOrAllocatePrefixEntry(aConfig.GetPrefix());
VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
entry->Publish(aConfig);
exit:
return error;
}
Error Publisher::PublishExternalRoute(const ExternalRouteConfig &aConfig)
{
Error error = kErrorNone;
PrefixEntry *entry;
VerifyOrExit(aConfig.IsValid(GetInstance()), error = kErrorInvalidArgs);
VerifyOrExit(aConfig.mStable, error = kErrorInvalidArgs);
entry = FindOrAllocatePrefixEntry(aConfig.GetPrefix());
VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
entry->Publish(aConfig);
exit:
return error;
}
bool Publisher::IsPrefixAdded(const Ip6::Prefix &aPrefix) const
{
bool isAdded = false;
const PrefixEntry *entry;
entry = FindMatchingPrefixEntry(aPrefix);
VerifyOrExit(entry != nullptr);
isAdded = entry->IsAdded();
exit:
return isAdded;
}
Error Publisher::UnpublishPrefix(const Ip6::Prefix &aPrefix)
{
Error error = kErrorNone;
PrefixEntry *entry;
entry = FindMatchingPrefixEntry(aPrefix);
VerifyOrExit(entry != nullptr, error = kErrorNotFound);
entry->Unpublish();
exit:
return error;
}
Publisher::PrefixEntry *Publisher::FindOrAllocatePrefixEntry(const Ip6::Prefix &aPrefix)
{
// Returns a matching prefix entry if found, otherwise tries
// to allocate a new entry.
PrefixEntry *prefixEntry = FindMatchingPrefixEntry(aPrefix);
VerifyOrExit(prefixEntry == nullptr);
for (PrefixEntry &entry : mPrefixEntries)
{
if (!entry.IsInUse())
{
prefixEntry = &entry;
ExitNow();
}
}
exit:
return prefixEntry;
}
Publisher::PrefixEntry *Publisher::FindMatchingPrefixEntry(const Ip6::Prefix &aPrefix)
{
return AsNonConst(AsConst(this)->FindMatchingPrefixEntry(aPrefix));
}
const Publisher::PrefixEntry *Publisher::FindMatchingPrefixEntry(const Ip6::Prefix &aPrefix) const
{
const PrefixEntry *prefixEntry = nullptr;
for (const PrefixEntry &entry : mPrefixEntries)
{
if (entry.IsInUse() && entry.Matches(aPrefix))
{
prefixEntry = &entry;
break;
}
}
return prefixEntry;
}
bool Publisher::IsAPrefixEntry(const Entry &aEntry) const
{
return (&mPrefixEntries[0] <= &aEntry) && (&aEntry < GetArrayEnd(mPrefixEntries));
}
void Publisher::NotifyPrefixEntryChange(Event aEvent, const Ip6::Prefix &aPrefix) const
{
if (mPrefixCallback != nullptr)
{
mPrefixCallback(static_cast<otNetDataPublisherEvent>(aEvent), &aPrefix, mPrefixCallbackContext);
}
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
void Publisher::HandleNotifierEvents(Events aEvents)
{
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
mDnsSrpServiceEntry.HandleNotifierEvents(aEvents);
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
for (PrefixEntry &entry : mPrefixEntries)
{
entry.HandleNotifierEvents(aEvents);
}
#endif
}
void Publisher::HandleTimer(Timer &aTimer)
{
aTimer.Get<Publisher>().HandleTimer();
}
void Publisher::HandleTimer(void)
{
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
mDnsSrpServiceEntry.HandleTimer();
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
for (PrefixEntry &entry : mPrefixEntries)
{
entry.HandleTimer();
}
#endif
}
//---------------------------------------------------------------------------------------------------------------------
// Publisher::Entry
void Publisher::Entry::SetState(State aState)
{
VerifyOrExit(mState != aState);
LogInfo("%s - State: %s -> %s", ToString(/* aIncludeState */ false).AsCString(), StateToString(mState),
StateToString(aState));
mState = aState;
exit:
return;
}
bool Publisher::Entry::IsPreferred(uint16_t aRloc16) const
{
// Indicates whether or not an entry from `aRloc16` is preferred
// over our entry (based on our RLOC). We prefer an entry from a
// router over an entry from an end-device (e.g., a REED). If both
// are the same type, then the one with smaller RLOC16 is preferred.
bool isOtherRouter = Mle::Mle::IsActiveRouter(aRloc16);
return (Get<Mle::Mle>().IsRouterOrLeader() == isOtherRouter) ? (aRloc16 < Get<Mle::Mle>().GetRloc16())
: isOtherRouter;
}
void Publisher::Entry::UpdateState(uint8_t aNumEntries, uint8_t aNumPreferredEntries, uint8_t aDesiredNumEntries)
{
// This method uses the info about number existing entries (total
// and preferred) in Network Data along with the desired number of
// entries we aim to have in the Network Data to decide whether or
// not to take any action (add or remove our entry).
LogInfo("%s in netdata - total:%d, preferred:%d, desired:%d", ToString().AsCString(), aNumEntries,
aNumPreferredEntries, aDesiredNumEntries);
switch (GetState())
{
case kNoEntry:
break;
case kToAdd:
// Our entry is ready to be added. If there are too few existing
// entries, we start adding our entry (start the timer with a
// random delay before adding the entry).
if (aNumEntries < aDesiredNumEntries)
{
mUpdateTime = TimerMilli::GetNow() + Random::NonCrypto::GetUint32InRange(1, kMaxDelayToAdd);
SetState(kAdding);
Get<Publisher>().GetTimer().FireAtIfEarlier(mUpdateTime);
LogUpdateTime();
}
break;
case kAdding:
// Our entry is being added (waiting time before we add). If we
// now see that there are enough entries, we stop adding the
// entry.
if (aNumEntries >= aDesiredNumEntries)
{
SetState(kToAdd);
}
break;
case kAdded:
// Our entry is already added in the Network Data. If there are
// enough entries, do nothing and keep monitoring. If we see now
// that there are too many entries, we start removing our entry
// after a random delay time. If our entry itself is preferred
// over other entries (indicated by `aNumPreferredEntries <
// aDesiredNumEntries`) we add an extra delay before removing
// the entry. This gives higher chance for a non-preferred
// entry from another device to be removed before our entry.
if (aNumEntries > aDesiredNumEntries)
{
mUpdateTime = TimerMilli::GetNow() + Random::NonCrypto::GetUint32InRange(1, kMaxDelayToRemove);
if (aNumPreferredEntries < aDesiredNumEntries)
{
mUpdateTime += kExtraDelayToRemovePeferred;
}
SetState(kRemoving);
Get<Publisher>().GetTimer().FireAtIfEarlier(mUpdateTime);
LogUpdateTime();
}
break;
case kRemoving:
// Our entry is being removed (wait time before remove). If we
// now see that there are enough or too few entries, we stop
// removing our entry.
if (aNumEntries <= aDesiredNumEntries)
{
SetState(kAdded);
}
break;
}
}
void Publisher::Entry::HandleTimer(void)
{
// Timer is used to delay adding/removing the entry. If we have
// reached `mUpdateTime` add or remove the entry. Otherwise,
// restart the timer (note that timer can be shared between
// different published entries).
VerifyOrExit((GetState() == kAdding) || (GetState() == kRemoving));
if (mUpdateTime <= TimerMilli::GetNow())
{
if (GetState() == kAdding)
{
Add();
}
else
{
Remove(/* aNextState */ kToAdd);
}
}
else
{
Get<Publisher>().GetTimer().FireAtIfEarlier(mUpdateTime);
}
exit:
return;
}
void Publisher::Entry::Add(void)
{
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
if (Get<Publisher>().IsADnsSrpServiceEntry(*this))
{
static_cast<DnsSrpServiceEntry *>(this)->Add();
}
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
if (Get<Publisher>().IsAPrefixEntry(*this))
{
static_cast<PrefixEntry *>(this)->Add();
}
#endif
}
void Publisher::Entry::Remove(State aNextState)
{
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
if (Get<Publisher>().IsADnsSrpServiceEntry(*this))
{
static_cast<DnsSrpServiceEntry *>(this)->Remove(aNextState);
}
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
if (Get<Publisher>().IsAPrefixEntry(*this))
{
static_cast<PrefixEntry *>(this)->Remove(aNextState);
}
#endif
}
Publisher::Entry::InfoString Publisher::Entry::ToString(bool aIncludeState) const
{
InfoString string;
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
if (Get<Publisher>().IsADnsSrpServiceEntry(*this))
{
string.Append("DNS/SRP service");
ExitNow();
}
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
if (Get<Publisher>().IsAPrefixEntry(*this))
{
const PrefixEntry &prefixEntry = *static_cast<const PrefixEntry *>(this);
switch (prefixEntry.mType)
{
case PrefixEntry::kTypeOnMeshPrefix:
string.Append("OnMeshPrefix ");
break;
case PrefixEntry::kTypeExternalRoute:
string.Append("ExternalRoute ");
break;
}
string.Append(prefixEntry.mPrefix.ToString().AsCString());
ExitNow();
}
#endif
exit:
if (aIncludeState)
{
string.Append(" (state:%s)", StateToString(GetState()));
}
return string;
}
void Publisher::Entry::LogUpdateTime(void) const
{
LogInfo("%s - update in %u msec", ToString().AsCString(), mUpdateTime - TimerMilli::GetNow());
}
const char *Publisher::Entry::StateToString(State aState)
{
static const char *const kStateStrings[] = {
"NoEntry", // (0) kNoEntry
"ToAdd", // (1) kToAdd
"Adding", // (2) kAdding
"Added", // (3) kAdded
"Removing", // (4) kRemoving
};
static_assert(0 == kNoEntry, "kNoEntry value is not correct");
static_assert(1 == kToAdd, "kToAdd value is not correct");
static_assert(2 == kAdding, "kAdding value is not correct");
static_assert(3 == kAdded, "kAdded value is not correct");
static_assert(4 == kRemoving, "kRemoving value is not correct");
return kStateStrings[aState];
}
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
//---------------------------------------------------------------------------------------------------------------------
// Publisher::DnsSrpServiceEntry
Publisher::DnsSrpServiceEntry::DnsSrpServiceEntry(Instance &aInstance)
: mCallback(nullptr)
, mCallbackContext(nullptr)
{
Init(aInstance);
}
void Publisher::DnsSrpServiceEntry::SetCallback(DnsSrpServiceCallback aCallback, void *aContext)
{
mCallback = aCallback;
mCallbackContext = aContext;
}
void Publisher::DnsSrpServiceEntry::PublishAnycast(uint8_t aSequenceNumber)
{
LogInfo("Publishing DNS/SRP service anycast (seq-num:%d)", aSequenceNumber);
Publish(Info::InfoAnycast(aSequenceNumber));
}
void Publisher::DnsSrpServiceEntry::PublishUnicast(const Ip6::Address &aAddress, uint16_t aPort)
{
LogInfo("Publishing DNS/SRP service unicast (%s, port:%d)", aAddress.ToString().AsCString(), aPort);
Publish(Info::InfoUnicast(kTypeUnicast, aAddress, aPort));
}
void Publisher::DnsSrpServiceEntry::PublishUnicast(uint16_t aPort)
{
LogInfo("Publishing DNS/SRP service unicast (ml-eid, port:%d)", aPort);
Publish(Info::InfoUnicast(kTypeUnicastMeshLocalEid, Get<Mle::Mle>().GetMeshLocal64(), aPort));
}
void Publisher::DnsSrpServiceEntry::Publish(const Info &aInfo)
{
if (GetState() != kNoEntry)
{
if (aInfo == mInfo)
{
LogInfo("%s is already being published", ToString().AsCString());
ExitNow();
}
Remove(/* aNextState */ kNoEntry);
}
mInfo = aInfo;
SetState(kToAdd);
Process();
exit:
return;
}
void Publisher::DnsSrpServiceEntry::Unpublish(void)
{
LogInfo("Unpublishing DNS/SRP service");
Remove(/* aNextState */ kNoEntry);
}
void Publisher::DnsSrpServiceEntry::HandleNotifierEvents(Events aEvents)
{
if ((GetType() == kTypeUnicastMeshLocalEid) && aEvents.Contains(kEventThreadMeshLocalAddrChanged))
{
mInfo.SetAddress(Get<Mle::Mle>().GetMeshLocal64());
if (GetState() == kAdded)
{
// If the entry is already added, we need to update it
// so we remove it and add it back immediately with
// the new mesh-local address.
Remove(/* aNextState */ kAdding);
Add();
Get<Notifier>().HandleServerDataUpdated();
}
}
if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadRoleChanged))
{
Process();
}
}
void Publisher::DnsSrpServiceEntry::Add(void)
{
// Adds the service entry to the network data.
switch (GetType())
{
case kTypeAnycast:
SuccessOrExit(Get<Service::Manager>().Add<Service::DnsSrpAnycast>(
Service::DnsSrpAnycast::ServiceData(mInfo.GetSequenceNumber())));
break;
case kTypeUnicast:
SuccessOrExit(Get<Service::Manager>().Add<Service::DnsSrpUnicast>(
Service::DnsSrpUnicast::ServiceData(mInfo.GetAddress(), mInfo.GetPort())));
break;
case kTypeUnicastMeshLocalEid:
SuccessOrExit(Get<Service::Manager>().Add<Service::DnsSrpUnicast>(
Service::DnsSrpUnicast::ServerData(mInfo.GetAddress(), mInfo.GetPort())));
break;
}
Get<Notifier>().HandleServerDataUpdated();
SetState(kAdded);
Notify(kEventEntryAdded);
exit:
return;
}
void Publisher::DnsSrpServiceEntry::Remove(State aNextState)
{
// Removes the service entry from network data (if it was added).
VerifyOrExit((GetState() == kAdded) || (GetState() == kRemoving));
switch (GetType())
{
case kTypeAnycast:
SuccessOrExit(Get<Service::Manager>().Remove<Service::DnsSrpAnycast>(
Service::DnsSrpAnycast::ServiceData(mInfo.GetSequenceNumber())));
break;
case kTypeUnicast:
SuccessOrExit(Get<Service::Manager>().Remove<Service::DnsSrpUnicast>(
Service::DnsSrpUnicast::ServiceData(mInfo.GetAddress(), mInfo.GetPort())));
break;
case kTypeUnicastMeshLocalEid:
SuccessOrExit(Get<Service::Manager>().Remove<Service::DnsSrpUnicast>());
break;
}
Get<Notifier>().HandleServerDataUpdated();
Notify(kEventEntryRemoved);
exit:
SetState(aNextState);
}
void Publisher::DnsSrpServiceEntry::Notify(Event aEvent) const
{
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Get<Srp::Server>().HandleNetDataPublisherEvent(aEvent);
#endif
if (mCallback != nullptr)
{
mCallback(static_cast<otNetDataPublisherEvent>(aEvent), mCallbackContext);
}
}
void Publisher::DnsSrpServiceEntry::Process(void)
{
// This method checks the entries currently present in Network Data
// based on which it then decides whether or not take action
// (add/remove or keep monitoring).
uint8_t numEntries = 0;
uint8_t numPreferredEntries = 0;
uint8_t desiredNumEntries = 0;
// Do not make any changes if device is not attached, and wait
// for role change event.
VerifyOrExit(Get<Mle::Mle>().IsAttached());
VerifyOrExit(GetState() != kNoEntry);
switch (GetType())
{
case kTypeAnycast:
CountAnycastEntries(numEntries, numPreferredEntries);
desiredNumEntries = kDesiredNumAnycast;
break;
case kTypeUnicast:
case kTypeUnicastMeshLocalEid:
CountUnicastEntries(numEntries, numPreferredEntries);
desiredNumEntries = kDesiredNumUnicast;
break;
}
UpdateState(numEntries, numPreferredEntries, desiredNumEntries);
exit:
return;
}
void Publisher::DnsSrpServiceEntry::CountAnycastEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const
{
// Count the number of matching "DNS/SRP Anycast" service entries
// in the Network Data (the match requires the entry to use same
// "sequence number" value). We prefer the entries associated with
// smaller RLCO16.
Service::DnsSrpAnycast::ServiceData serviceData(mInfo.GetSequenceNumber());
const ServiceTlv * serviceTlv = nullptr;
ServiceData data;
data.Init(&serviceData, serviceData.GetLength());
while ((serviceTlv = Get<Leader>().FindNextThreadService(serviceTlv, data, NetworkData::kServicePrefixMatch)) !=
nullptr)
{
TlvIterator subTlvIterator(*serviceTlv);
const ServerTlv *serverSubTlv;
while ((serverSubTlv = subTlvIterator.Iterate<ServerTlv>()) != nullptr)
{
aNumEntries++;
if (IsPreferred(serverSubTlv->GetServer16()))
{
aNumPreferredEntries++;
}
}
}
}
void Publisher::DnsSrpServiceEntry::CountUnicastEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const
{
// Count the number of "DNS/SRP Unicast" service entries in
// the Network Data.
const ServiceTlv *serviceTlv = nullptr;
ServiceData data;
data.InitFrom(Service::DnsSrpUnicast::kServiceData);
while ((serviceTlv = Get<Leader>().FindNextThreadService(serviceTlv, data, NetworkData::kServicePrefixMatch)) !=
nullptr)
{
TlvIterator subTlvIterator(*serviceTlv);
const ServerTlv *serverSubTlv;
while (((serverSubTlv = subTlvIterator.Iterate<ServerTlv>())) != nullptr)
{
if (serviceTlv->GetServiceDataLength() >= sizeof(Service::DnsSrpUnicast::ServiceData))
{
aNumEntries++;
// Generally, we prefer entries where the SRP/DNS server
// address/port info is included in the service TLV data
// over the ones where the info is included in the
// server TLV data (i.e., we prefer infra-provided
// SRP/DNS entry over a BR local one using ML-EID). If
// our entry itself uses the service TLV data, then we
// prefer based on the associated RLOC16.
if (GetType() == kTypeUnicast)
{
if (IsPreferred(serverSubTlv->GetServer16()))
{
aNumPreferredEntries++;
}
}
else
{
aNumPreferredEntries++;
}
}
if (serverSubTlv->GetServerDataLength() >= sizeof(Service::DnsSrpUnicast::ServerData))
{
aNumEntries++;
// If our entry also uses the server TLV data (with
// ML-EID address), then the we prefer based on the
// associated RLOC16.
if ((GetType() == kTypeUnicastMeshLocalEid) && IsPreferred(serverSubTlv->GetServer16()))
{
aNumPreferredEntries++;
}
}
}
}
}
//---------------------------------------------------------------------------------------------------------------------
// Publisher::DnsSrpServiceEntry::Info
Publisher::DnsSrpServiceEntry::Info::Info(Type aType, uint16_t aPortOrSeqNumber, const Ip6::Address *aAddress)
: mPortOrSeqNumber(aPortOrSeqNumber)
, mType(aType)
{
// It is important to `Clear()` the object since we compare all
// bytes using overload of operator `==`.
Clear();
mType = aType;
mPortOrSeqNumber = aPortOrSeqNumber;
if (aAddress != nullptr)
{
mAddress = *aAddress;
}
}
#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
//---------------------------------------------------------------------------------------------------------------------
// Publisher::PrefixEntry
void Publisher::PrefixEntry::Publish(const OnMeshPrefixConfig &aConfig)
{
LogInfo("Publishing OnMeshPrefix %s", aConfig.GetPrefix().ToString().AsCString());
Publish(aConfig.GetPrefix(), aConfig.ConvertToTlvFlags(), kTypeOnMeshPrefix);
}
void Publisher::PrefixEntry::Publish(const ExternalRouteConfig &aConfig)
{
LogInfo("Publishing ExternalRoute %s", aConfig.GetPrefix().ToString().AsCString());
Publish(aConfig.GetPrefix(), aConfig.ConvertToTlvFlags(), kTypeExternalRoute);
}
void Publisher::PrefixEntry::Publish(const Ip6::Prefix &aPrefix, uint16_t aNewFlags, Type aNewType)
{
if (GetState() != kNoEntry)
{
// If this is an existing entry, first we check that there is
// a change in either type or flags. We remove the old entry
// from Network Data if it was added. If the only change is
// to flags (e.g., change to the preference level) and the
// entry was previously added in Network Data, we re-add it
// with the new flags. This ensures that changes to flags are
// immediately reflected in the Network Data.
State oldState = GetState();
VerifyOrExit((mType != aNewType) || (mFlags != aNewFlags));
Remove(/* aNextState */ kNoEntry);
if ((mType == aNewType) && ((oldState == kAdded) || (oldState == kRemoving)))
{
mFlags = aNewFlags;
Add();
}
}
VerifyOrExit(GetState() == kNoEntry);
mType = aNewType;
mPrefix = aPrefix;
mFlags = aNewFlags;
SetState(kToAdd);
exit:
Process();
}
void Publisher::PrefixEntry::Unpublish(void)
{
LogInfo("Unpublishing %s", mPrefix.ToString().AsCString());
Remove(/* aNextState */ kNoEntry);
}
void Publisher::PrefixEntry::HandleNotifierEvents(Events aEvents)
{
if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadRoleChanged))
{
Process();
}
}
void Publisher::PrefixEntry::Add(void)
{
// Adds the prefix entry to the network data.
switch (mType)
{
case kTypeOnMeshPrefix:
SuccessOrExit(AddOnMeshPrefix());
break;
case kTypeExternalRoute:
SuccessOrExit(AddExternalRoute());
break;
}
Get<Notifier>().HandleServerDataUpdated();
SetState(kAdded);
Get<Publisher>().NotifyPrefixEntryChange(kEventEntryAdded, mPrefix);
exit:
return;
}
Error Publisher::PrefixEntry::AddOnMeshPrefix(void)
{
OnMeshPrefixConfig config;
config.mPrefix = mPrefix;
config.mStable = true;
config.SetFromTlvFlags(mFlags);
return Get<Local>().AddOnMeshPrefix(config);
}
Error Publisher::PrefixEntry::AddExternalRoute(void)
{
ExternalRouteConfig config;
config.mPrefix = mPrefix;
config.mStable = true;
config.SetFromTlvFlags(static_cast<uint8_t>(mFlags));
return Get<Local>().AddHasRoutePrefix(config);
}
void Publisher::PrefixEntry::Remove(State aNextState)
{
// Remove the prefix entry from the network data.
VerifyOrExit((GetState() == kAdded) || (GetState() == kRemoving));
switch (mType)
{
case kTypeOnMeshPrefix:
IgnoreError(Get<Local>().RemoveOnMeshPrefix(mPrefix));
break;
case kTypeExternalRoute:
IgnoreError(Get<Local>().RemoveHasRoutePrefix(mPrefix));
break;
}
Get<Notifier>().HandleServerDataUpdated();
Get<Publisher>().NotifyPrefixEntryChange(kEventEntryRemoved, mPrefix);
exit:
SetState(aNextState);
}
void Publisher::PrefixEntry::Process(void)
{
// This method checks the entries currently present in Network Data
// based on which it then decides whether or not take action
// (add/remove or keep monitoring).
uint8_t numEntries = 0;
uint8_t numPreferredEntries = 0;
uint8_t desiredNumEntries = 0;
// Do not make any changes if device is not attached, and wait
// for role change event.
VerifyOrExit(Get<Mle::Mle>().IsAttached());
VerifyOrExit(GetState() != kNoEntry);
switch (mType)
{
case kTypeOnMeshPrefix:
CountOnMeshPrefixEntries(numEntries, numPreferredEntries);
desiredNumEntries = kDesiredNumOnMeshPrefix;
break;
case kTypeExternalRoute:
CountExternalRouteEntries(numEntries, numPreferredEntries);
desiredNumEntries = kDesiredNumExternalRoute;
break;
}
UpdateState(numEntries, numPreferredEntries, desiredNumEntries);
exit:
return;
}
void Publisher::PrefixEntry::CountOnMeshPrefixEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const
{
const PrefixTlv * prefixTlv;
const BorderRouterTlv *brSubTlv;
int8_t preference = BorderRouterEntry::PreferenceFromFlags(mFlags);
uint16_t flagsWithoutPreference = BorderRouterEntry::FlagsWithoutPreference(mFlags);
prefixTlv = Get<Leader>().FindPrefix(mPrefix);
VerifyOrExit(prefixTlv != nullptr);
brSubTlv = prefixTlv->FindSubTlv<BorderRouterTlv>(/* aStable */ true);
VerifyOrExit(brSubTlv != nullptr);
for (const BorderRouterEntry *entry = brSubTlv->GetFirstEntry(); entry <= brSubTlv->GetLastEntry();
entry = entry->GetNext())
{
uint16_t entryFlags = entry->GetFlags();
int8_t entryPreference = BorderRouterEntry::PreferenceFromFlags(entryFlags);
// Count an existing entry in the network data if its flags
// match ours and and its preference is same or higher than our
// preference. We do not count matching entries at a lower
// preference than ours. This ensures that a device with higher
// preference entry publishes its entry even when there are many
// lower preference similar entries in the network data
// (potentially causing a lower preference entry to be removed).
if ((BorderRouterEntry::FlagsWithoutPreference(entryFlags) == flagsWithoutPreference) &&
(entryPreference >= preference))
{
aNumEntries++;
// We prefer an entry if it has strictly higher preference
// than ours or if it has same preference we use the associated
// RLOC16.
if ((entryPreference > preference) || IsPreferred(entry->GetRloc()))
{
aNumPreferredEntries++;
}
}
}
exit:
return;
}
void Publisher::PrefixEntry::CountExternalRouteEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const
{
const PrefixTlv * prefixTlv;
const HasRouteTlv *hrSubTlv;
int8_t preference = HasRouteEntry::PreferenceFromFlags(static_cast<uint8_t>(mFlags));
uint8_t flagsWithoutPreference = HasRouteEntry::FlagsWithoutPreference(static_cast<uint8_t>(mFlags));
prefixTlv = Get<Leader>().FindPrefix(mPrefix);
VerifyOrExit(prefixTlv != nullptr);
hrSubTlv = prefixTlv->FindSubTlv<HasRouteTlv>(/* aStable */ true);
VerifyOrExit(hrSubTlv != nullptr);
for (const HasRouteEntry *entry = hrSubTlv->GetFirstEntry(); entry <= hrSubTlv->GetLastEntry();
entry = entry->GetNext())
{
uint8_t entryFlags = entry->GetFlags();
int8_t entryPreference = HasRouteEntry::PreferenceFromFlags(entryFlags);
// Count an existing entry in the network data if its flags
// match ours and and its preference is same or higher than our
// preference. We do not count matching entries at a lower
// preference than ours. This ensures that a device with higher
// preference entry publishes its entry even when there are many
// lower preference similar entries in the network data
// (potentially causing a lower preference entry to be removed).
if ((HasRouteEntry::FlagsWithoutPreference(entryFlags) == flagsWithoutPreference) &&
(entryPreference >= preference))
{
aNumEntries++;
// We prefer an entry if it has strictly higher preference
// than ours or if it has same preference with a smaller
// RLOC16.
if ((entryPreference > preference) || IsPreferred(entry->GetRloc()))
{
aNumPreferredEntries++;
}
}
}
exit:
return;
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
} // namespace NetworkData
} // namespace ot
#endif // OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE