/*
 *  Copyright (c) 2016, 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 common methods for manipulating Thread Network Data.
 */

#include "network_data.hpp"

#include "coap/coap_message.hpp"
#include "common/array.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "mac/mac_types.hpp"
#include "thread/thread_netif.hpp"
#include "thread/thread_tlvs.hpp"
#include "thread/uri_paths.hpp"

namespace ot {
namespace NetworkData {

RegisterLogModule("NetworkData");

Error NetworkData::CopyNetworkData(Type aType, uint8_t *aData, uint8_t &aDataLength) const
{
    Error              error;
    MutableNetworkData netDataCopy(GetInstance(), aData, 0, aDataLength);

    SuccessOrExit(error = CopyNetworkData(aType, netDataCopy));
    aDataLength = netDataCopy.GetLength();

exit:
    return error;
}

Error NetworkData::CopyNetworkData(Type aType, MutableNetworkData &aNetworkData) const
{
    Error error = kErrorNone;

    VerifyOrExit(aNetworkData.GetSize() >= mLength, error = kErrorNoBufs);

    memcpy(aNetworkData.GetBytes(), mTlvs, mLength);
    aNetworkData.SetLength(mLength);

    if (aType == kStableSubset)
    {
        aNetworkData.RemoveTemporaryData();
    }

exit:
    return error;
}

Error NetworkData::GetNextOnMeshPrefix(Iterator &aIterator, OnMeshPrefixConfig &aConfig) const
{
    return GetNextOnMeshPrefix(aIterator, Mac::kShortAddrBroadcast, aConfig);
}

Error NetworkData::GetNextOnMeshPrefix(Iterator &aIterator, uint16_t aRloc16, OnMeshPrefixConfig &aConfig) const
{
    Config config;

    config.mOnMeshPrefix  = &aConfig;
    config.mExternalRoute = nullptr;
    config.mService       = nullptr;

    return Iterate(aIterator, aRloc16, config);
}

Error NetworkData::GetNextExternalRoute(Iterator &aIterator, ExternalRouteConfig &aConfig) const
{
    return GetNextExternalRoute(aIterator, Mac::kShortAddrBroadcast, aConfig);
}

Error NetworkData::GetNextExternalRoute(Iterator &aIterator, uint16_t aRloc16, ExternalRouteConfig &aConfig) const
{
    Config config;

    config.mOnMeshPrefix  = nullptr;
    config.mExternalRoute = &aConfig;
    config.mService       = nullptr;

    return Iterate(aIterator, aRloc16, config);
}

Error NetworkData::GetNextService(Iterator &aIterator, ServiceConfig &aConfig) const
{
    return GetNextService(aIterator, Mac::kShortAddrBroadcast, aConfig);
}

Error NetworkData::GetNextService(Iterator &aIterator, uint16_t aRloc16, ServiceConfig &aConfig) const
{
    Config config;

    config.mOnMeshPrefix  = nullptr;
    config.mExternalRoute = nullptr;
    config.mService       = &aConfig;

    return Iterate(aIterator, aRloc16, config);
}

Error NetworkData::Iterate(Iterator &aIterator, uint16_t aRloc16, Config &aConfig) const
{
    // Iterate to the next entry in Network Data matching `aRloc16`
    // (can be set to `Mac::kShortAddrBroadcast` to allow any RLOC).
    // The `aIterator` is used to track and save the current position.
    // On input, the non-`nullptr` pointer members in `aConfig` specify
    // the Network Data entry types (`mOnMeshPrefix`, `mExternalRoute`,
    // `mService`) to iterate over. On successful exit, the `aConfig`
    // is updated such that only one member pointer is not `nullptr`
    // indicating the type of entry and the non-`nullptr` config is
    // updated with the entry info.

    Error               error = kErrorNotFound;
    NetworkDataIterator iterator(aIterator);

    for (const NetworkDataTlv *cur;
         cur = iterator.GetTlv(mTlvs), (cur + 1 <= GetTlvsEnd()) && (cur->GetNext() <= GetTlvsEnd());
         iterator.AdvanceTlv(mTlvs))
    {
        const NetworkDataTlv *subTlvs = nullptr;

        switch (cur->GetType())
        {
        case NetworkDataTlv::kTypePrefix:
            if ((aConfig.mOnMeshPrefix != nullptr) || (aConfig.mExternalRoute != nullptr))
            {
                subTlvs = As<PrefixTlv>(cur)->GetSubTlvs();
            }
            break;
        case NetworkDataTlv::kTypeService:
            if (aConfig.mService != nullptr)
            {
                subTlvs = As<ServiceTlv>(cur)->GetSubTlvs();
            }
            break;
        default:
            break;
        }

        if (subTlvs == nullptr)
        {
            continue;
        }

        for (const NetworkDataTlv *subCur; subCur = iterator.GetSubTlv(subTlvs),
                                           (subCur + 1 <= cur->GetNext()) && (subCur->GetNext() <= cur->GetNext());
             iterator.AdvaceSubTlv(subTlvs))
        {
            if (cur->GetType() == NetworkDataTlv::kTypePrefix)
            {
                const PrefixTlv *prefixTlv = As<PrefixTlv>(cur);

                switch (subCur->GetType())
                {
                case NetworkDataTlv::kTypeBorderRouter:
                {
                    const BorderRouterTlv *borderRouter = As<BorderRouterTlv>(subCur);

                    if (aConfig.mOnMeshPrefix == nullptr)
                    {
                        continue;
                    }

                    for (uint8_t index; (index = iterator.GetAndAdvanceIndex()) < borderRouter->GetNumEntries();)
                    {
                        if (aRloc16 == Mac::kShortAddrBroadcast || borderRouter->GetEntry(index)->GetRloc() == aRloc16)
                        {
                            const BorderRouterEntry *borderRouterEntry = borderRouter->GetEntry(index);

                            aConfig.mExternalRoute = nullptr;
                            aConfig.mService       = nullptr;
                            aConfig.mOnMeshPrefix->SetFrom(*prefixTlv, *borderRouter, *borderRouterEntry);

                            ExitNow(error = kErrorNone);
                        }
                    }

                    break;
                }

                case NetworkDataTlv::kTypeHasRoute:
                {
                    const HasRouteTlv *hasRoute = As<HasRouteTlv>(subCur);

                    if (aConfig.mExternalRoute == nullptr)
                    {
                        continue;
                    }

                    for (uint8_t index; (index = iterator.GetAndAdvanceIndex()) < hasRoute->GetNumEntries();)
                    {
                        if (aRloc16 == Mac::kShortAddrBroadcast || hasRoute->GetEntry(index)->GetRloc() == aRloc16)
                        {
                            const HasRouteEntry *hasRouteEntry = hasRoute->GetEntry(index);

                            aConfig.mOnMeshPrefix = nullptr;
                            aConfig.mService      = nullptr;
                            aConfig.mExternalRoute->SetFrom(GetInstance(), *prefixTlv, *hasRoute, *hasRouteEntry);

                            ExitNow(error = kErrorNone);
                        }
                    }

                    break;
                }

                default:
                    break;
                }
            }
            else // cur is `ServiceTLv`
            {
                const ServiceTlv *service = As<ServiceTlv>(cur);

                if (aConfig.mService == nullptr)
                {
                    continue;
                }

                if (subCur->GetType() == NetworkDataTlv::kTypeServer)
                {
                    const ServerTlv *server = As<ServerTlv>(subCur);

                    if (!iterator.IsNewEntry())
                    {
                        continue;
                    }

                    if ((aRloc16 == Mac::kShortAddrBroadcast) || (server->GetServer16() == aRloc16))
                    {
                        aConfig.mOnMeshPrefix  = nullptr;
                        aConfig.mExternalRoute = nullptr;
                        aConfig.mService->SetFrom(*service, *server);

                        iterator.MarkEntryAsNotNew();

                        ExitNow(error = kErrorNone);
                    }
                }
            }
        }
    }

exit:
    return error;
}

bool NetworkData::ContainsOnMeshPrefix(const OnMeshPrefixConfig &aPrefix) const
{
    bool               contains = false;
    Iterator           iterator = kIteratorInit;
    OnMeshPrefixConfig prefix;

    while (GetNextOnMeshPrefix(iterator, aPrefix.mRloc16, prefix) == kErrorNone)
    {
        if (prefix == aPrefix)
        {
            contains = true;
            break;
        }
    }

    return contains;
}

bool NetworkData::ContainsExternalRoute(const ExternalRouteConfig &aRoute) const
{
    bool                contains = false;
    Iterator            iterator = kIteratorInit;
    ExternalRouteConfig route;

    while (GetNextExternalRoute(iterator, aRoute.mRloc16, route) == kErrorNone)
    {
        if (route == aRoute)
        {
            contains = true;
            break;
        }
    }

    return contains;
}

bool NetworkData::ContainsService(const ServiceConfig &aService) const
{
    bool          contains = false;
    Iterator      iterator = kIteratorInit;
    ServiceConfig service;

    while (GetNextService(iterator, aService.GetServerConfig().mRloc16, service) == kErrorNone)
    {
        if (service == aService)
        {
            contains = true;
            break;
        }
    }

    return contains;
}

bool NetworkData::ContainsEntriesFrom(const NetworkData &aCompare, uint16_t aRloc16) const
{
    bool     contains = true;
    Iterator iterator = kIteratorInit;

    while (true)
    {
        Config              config;
        OnMeshPrefixConfig  prefix;
        ExternalRouteConfig route;
        ServiceConfig       service;

        config.mOnMeshPrefix  = &prefix;
        config.mExternalRoute = &route;
        config.mService       = &service;

        SuccessOrExit(aCompare.Iterate(iterator, aRloc16, config));

        if (((config.mOnMeshPrefix != nullptr) && !ContainsOnMeshPrefix(*config.mOnMeshPrefix)) ||
            ((config.mExternalRoute != nullptr) && !ContainsExternalRoute(*config.mExternalRoute)) ||
            ((config.mService != nullptr) && !ContainsService(*config.mService)))
        {
            ExitNow(contains = false);
        }
    }

exit:
    return contains;
}

void MutableNetworkData::RemoveTemporaryData(void)
{
    NetworkDataTlv *cur = GetTlvsStart();

    while (cur < GetTlvsEnd())
    {
        switch (cur->GetType())
        {
        case NetworkDataTlv::kTypePrefix:
        {
            PrefixTlv *prefix = As<PrefixTlv>(cur);

            RemoveTemporaryDataIn(*prefix);

            if (prefix->GetSubTlvsLength() == 0)
            {
                RemoveTlv(cur);
                continue;
            }

            break;
        }

        case NetworkDataTlv::kTypeService:
        {
            ServiceTlv *service = As<ServiceTlv>(cur);

            RemoveTemporaryDataIn(*service);

            if (service->GetSubTlvsLength() == 0)
            {
                RemoveTlv(cur);
                continue;
            }

            break;
        }

        default:
            // remove temporary tlv
            if (!cur->IsStable())
            {
                RemoveTlv(cur);
                continue;
            }

            break;
        }

        cur = cur->GetNext();
    }
}

void MutableNetworkData::RemoveTemporaryDataIn(PrefixTlv &aPrefix)
{
    NetworkDataTlv *cur = aPrefix.GetSubTlvs();

    while (cur < aPrefix.GetNext())
    {
        if (cur->IsStable())
        {
            switch (cur->GetType())
            {
            case NetworkDataTlv::kTypeBorderRouter:
            {
                BorderRouterTlv *borderRouter = As<BorderRouterTlv>(cur);
                ContextTlv *     context      = aPrefix.FindSubTlv<ContextTlv>();

                // Replace p_border_router_16
                for (BorderRouterEntry *entry = borderRouter->GetFirstEntry(); entry <= borderRouter->GetLastEntry();
                     entry                    = entry->GetNext())
                {
                    if ((entry->IsDhcp() || entry->IsConfigure()) && (context != nullptr))
                    {
                        entry->SetRloc(0xfc00 | context->GetContextId());
                    }
                    else
                    {
                        entry->SetRloc(0xfffe);
                    }
                }

                break;
            }

            case NetworkDataTlv::kTypeHasRoute:
            {
                HasRouteTlv *hasRoute = As<HasRouteTlv>(cur);

                // Replace r_border_router_16
                for (HasRouteEntry *entry = hasRoute->GetFirstEntry(); entry <= hasRoute->GetLastEntry();
                     entry                = entry->GetNext())
                {
                    entry->SetRloc(0xfffe);
                }

                break;
            }

            default:
                break;
            }

            // keep stable tlv
            cur = cur->GetNext();
        }
        else
        {
            // remove temporary tlv
            uint8_t subTlvSize = cur->GetSize();
            RemoveTlv(cur);
            aPrefix.SetSubTlvsLength(aPrefix.GetSubTlvsLength() - subTlvSize);
        }
    }
}

void MutableNetworkData::RemoveTemporaryDataIn(ServiceTlv &aService)
{
    NetworkDataTlv *cur = aService.GetSubTlvs();

    while (cur < aService.GetNext())
    {
        if (cur->IsStable())
        {
            switch (cur->GetType())
            {
            case NetworkDataTlv::kTypeServer:
                As<ServerTlv>(cur)->SetServer16(Mle::Mle::ServiceAlocFromId(aService.GetServiceId()));
                break;

            default:
                break;
            }

            // keep stable tlv
            cur = cur->GetNext();
        }
        else
        {
            // remove temporary tlv
            uint8_t subTlvSize = cur->GetSize();
            RemoveTlv(cur);
            aService.SetSubTlvsLength(aService.GetSubTlvsLength() - subTlvSize);
        }
    }
}

const PrefixTlv *NetworkData::FindPrefix(const uint8_t *aPrefix, uint8_t aPrefixLength) const
{
    TlvIterator      tlvIterator(mTlvs, mLength);
    const PrefixTlv *prefixTlv;

    while ((prefixTlv = tlvIterator.Iterate<PrefixTlv>()) != nullptr)
    {
        if (prefixTlv->IsEqual(aPrefix, aPrefixLength))
        {
            break;
        }
    }

    return prefixTlv;
}

const ServiceTlv *NetworkData::FindService(uint32_t           aEnterpriseNumber,
                                           const ServiceData &aServiceData,
                                           ServiceMatchMode   aServiceMatchMode) const
{
    TlvIterator       tlvIterator(mTlvs, mLength);
    const ServiceTlv *serviceTlv;

    while ((serviceTlv = tlvIterator.Iterate<ServiceTlv>()) != nullptr)
    {
        if (MatchService(*serviceTlv, aEnterpriseNumber, aServiceData, aServiceMatchMode))
        {
            break;
        }
    }

    return serviceTlv;
}

const ServiceTlv *NetworkData::FindNextService(const ServiceTlv * aPrevServiceTlv,
                                               uint32_t           aEnterpriseNumber,
                                               const ServiceData &aServiceData,
                                               ServiceMatchMode   aServiceMatchMode) const
{
    const uint8_t *tlvs;
    uint8_t        length;

    if (aPrevServiceTlv == nullptr)
    {
        tlvs   = mTlvs;
        length = mLength;
    }
    else
    {
        tlvs   = reinterpret_cast<const uint8_t *>(aPrevServiceTlv->GetNext());
        length = static_cast<uint8_t>((mTlvs + mLength) - tlvs);
    }

    return NetworkData(GetInstance(), tlvs, length).FindService(aEnterpriseNumber, aServiceData, aServiceMatchMode);
}

const ServiceTlv *NetworkData::FindNextThreadService(const ServiceTlv * aPrevServiceTlv,
                                                     const ServiceData &aServiceData,
                                                     ServiceMatchMode   aServiceMatchMode) const
{
    return FindNextService(aPrevServiceTlv, ServiceTlv::kThreadEnterpriseNumber, aServiceData, aServiceMatchMode);
}

bool NetworkData::MatchService(const ServiceTlv & aServiceTlv,
                               uint32_t           aEnterpriseNumber,
                               const ServiceData &aServiceData,
                               ServiceMatchMode   aServiceMatchMode)
{
    bool        match = false;
    ServiceData serviceData;

    VerifyOrExit(aServiceTlv.GetEnterpriseNumber() == aEnterpriseNumber);

    aServiceTlv.GetServiceData(serviceData);

    switch (aServiceMatchMode)
    {
    case kServiceExactMatch:
        match = (serviceData == aServiceData);
        break;

    case kServicePrefixMatch:
        match = serviceData.StartsWith(aServiceData);
        break;
    }

exit:
    return match;
}

NetworkDataTlv *MutableNetworkData::AppendTlv(uint16_t aTlvSize)
{
    NetworkDataTlv *tlv;

    VerifyOrExit(CanInsert(aTlvSize), tlv = nullptr);

    tlv = GetTlvsEnd();
    mLength += static_cast<uint8_t>(aTlvSize);

exit:
    return tlv;
}

void MutableNetworkData::Insert(void *aStart, uint8_t aLength)
{
    uint8_t *start = reinterpret_cast<uint8_t *>(aStart);

    OT_ASSERT(CanInsert(aLength) && mTlvs <= start && start <= mTlvs + mLength);
    memmove(start + aLength, start, mLength - static_cast<size_t>(start - mTlvs));
    mLength += aLength;
}

void MutableNetworkData::Remove(void *aRemoveStart, uint8_t aRemoveLength)
{
    uint8_t *end         = GetBytes() + mLength;
    uint8_t *removeStart = reinterpret_cast<uint8_t *>(aRemoveStart);
    uint8_t *removeEnd   = removeStart + aRemoveLength;

    OT_ASSERT((aRemoveLength <= mLength) && (GetBytes() <= removeStart) && (removeEnd <= end));

    memmove(removeStart, removeEnd, static_cast<uint8_t>(end - removeEnd));
    mLength -= aRemoveLength;
}

void MutableNetworkData::RemoveTlv(NetworkDataTlv *aTlv)
{
    Remove(aTlv, aTlv->GetSize());
}

Error NetworkData::SendServerDataNotification(uint16_t              aRloc16,
                                              bool                  aAppendNetDataTlv,
                                              Coap::ResponseHandler aHandler,
                                              void *                aContext) const
{
    Error            error = kErrorNone;
    Coap::Message *  message;
    Tmf::MessageInfo messageInfo(GetInstance());

    message = Get<Tmf::Agent>().NewPriorityConfirmablePostMessage(UriPath::kServerData);
    VerifyOrExit(message != nullptr, error = kErrorNoBufs);

    if (aAppendNetDataTlv)
    {
        ThreadTlv tlv;
        tlv.SetType(ThreadTlv::kThreadNetworkData);
        tlv.SetLength(mLength);
        SuccessOrExit(error = message->Append(tlv));
        SuccessOrExit(error = message->AppendBytes(mTlvs, mLength));
    }

    if (aRloc16 != Mac::kShortAddrInvalid)
    {
        SuccessOrExit(error = Tlv::Append<ThreadRloc16Tlv>(*message, aRloc16));
    }

    IgnoreError(messageInfo.SetSockAddrToRlocPeerAddrToLeaderAloc());
    SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo, aHandler, aContext));

    LogInfo("Sent server data notification");

exit:
    FreeMessageOnError(message, error);
    return error;
}

Error NetworkData::GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const
{
    Error               error;
    OnMeshPrefixConfig  prefixConfig;
    ExternalRouteConfig routeConfig;
    ServiceConfig       serviceConfig;
    Config              config;

    config.mOnMeshPrefix  = &prefixConfig;
    config.mExternalRoute = &routeConfig;
    config.mService       = &serviceConfig;

    SuccessOrExit(error = Iterate(aIterator, Mac::kShortAddrBroadcast, config));

    if (config.mOnMeshPrefix != nullptr)
    {
        aRloc16 = config.mOnMeshPrefix->mRloc16;
    }
    else if (config.mExternalRoute != nullptr)
    {
        aRloc16 = config.mExternalRoute->mRloc16;
    }
    else if (config.mService != nullptr)
    {
        aRloc16 = config.mService->mServerConfig.mRloc16;
    }
    else
    {
        OT_ASSERT(false);
    }

exit:
    return error;
}

Error NetworkData::FindBorderRouters(RoleFilter aRoleFilter, uint16_t aRlocs[], uint8_t &aRlocsLength) const
{
    class Rlocs // Wrapper over an array of RLOC16s.
    {
    public:
        Rlocs(RoleFilter aRoleFilter, uint16_t *aRlocs, uint8_t aRlocsMaxLength)
            : mRoleFilter(aRoleFilter)
            , mRlocs(aRlocs)
            , mLength(0)
            , mMaxLength(aRlocsMaxLength)
        {
        }

        uint8_t GetLength(void) const { return mLength; }

        Error AddRloc16(uint16_t aRloc16)
        {
            // Add `aRloc16` into the array if it matches `RoleFilter` and
            // it is not in the array already. If we need to add the `aRloc16`
            // but there is no more room in the array, return `kErrorNoBufs`.

            Error   error = kErrorNone;
            uint8_t index;

            switch (mRoleFilter)
            {
            case kAnyRole:
                break;

            case kRouterRoleOnly:
                VerifyOrExit(Mle::Mle::IsActiveRouter(aRloc16));
                break;

            case kChildRoleOnly:
                VerifyOrExit(!Mle::Mle::IsActiveRouter(aRloc16));
                break;
            }

            for (index = 0; index < mLength; index++)
            {
                if (mRlocs[index] == aRloc16)
                {
                    break;
                }
            }

            if (index == mLength)
            {
                VerifyOrExit(mLength < mMaxLength, error = kErrorNoBufs);
                mRlocs[mLength++] = aRloc16;
            }

        exit:
            return error;
        }

    private:
        RoleFilter mRoleFilter;
        uint16_t * mRlocs;
        uint8_t    mLength;
        uint8_t    mMaxLength;
    };

    Error               error = kErrorNone;
    Rlocs               rlocs(aRoleFilter, aRlocs, aRlocsLength);
    Iterator            iterator = kIteratorInit;
    ExternalRouteConfig route;
    OnMeshPrefixConfig  prefix;

    while (GetNextExternalRoute(iterator, route) == kErrorNone)
    {
        SuccessOrExit(error = rlocs.AddRloc16(route.mRloc16));
    }

    iterator = kIteratorInit;

    while (GetNextOnMeshPrefix(iterator, prefix) == kErrorNone)
    {
        if (!prefix.mDefaultRoute || !prefix.mOnMesh)
        {
            continue;
        }

        SuccessOrExit(error = rlocs.AddRloc16(prefix.mRloc16));
    }

exit:
    aRlocsLength = rlocs.GetLength();
    return error;
}

uint8_t NetworkData::CountBorderRouters(RoleFilter aRoleFilter) const
{
    // We use an over-estimate of max number of border routers in the
    // Network Data using the facts that network data is limited to 254
    // bytes and that an external route entry uses at minimum 3 bytes
    // for RLOC16 and flag, so `ceil(254/3) = 85`.

    static constexpr uint16_t kMaxRlocs = 85;

    uint16_t rlocs[kMaxRlocs];
    uint8_t  rlocsLength = kMaxRlocs;

    SuccessOrAssert(FindBorderRouters(aRoleFilter, rlocs, rlocsLength));

    return rlocsLength;
}

bool NetworkData::ContainsBorderRouterWithRloc(uint16_t aRloc16) const
{
    bool                contains = false;
    Iterator            iterator = kIteratorInit;
    ExternalRouteConfig route;
    OnMeshPrefixConfig  prefix;

    while (GetNextExternalRoute(iterator, route) == kErrorNone)
    {
        if (route.mRloc16 == aRloc16)
        {
            ExitNow(contains = true);
        }
    }

    iterator = kIteratorInit;

    while (GetNextOnMeshPrefix(iterator, prefix) == kErrorNone)
    {
        if ((prefix.mRloc16 == aRloc16) && prefix.mOnMesh && (prefix.mDefaultRoute || prefix.mDp))
        {
            ExitNow(contains = true);
        }
    }

exit:
    return contains;
}

} // namespace NetworkData
} // namespace ot
