/*
 *  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 IPv6 network interfaces.
 */

#include "netif.hpp"

#include "common/as_core_type.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/message.hpp"
#include "net/ip6.hpp"

namespace ot {
namespace Ip6 {

class AddressInfo : public otIp6AddressInfo
{
public:
    explicit AddressInfo(const Netif::UnicastAddress &aAddress)
    {
        mAddress      = &aAddress.mAddress;
        mPrefixLength = aAddress.mPrefixLength;
        mScope        = aAddress.GetScope();
        mPreferred    = aAddress.mPreferred;
    }

    explicit AddressInfo(const Netif::MulticastAddress &aAddress)
    {
        mAddress      = &aAddress.GetAddress();
        mPrefixLength = kMulticastPrefixLength;
        mScope        = aAddress.GetAddress().GetScope();
        mPreferred    = false;
    }

private:
    static constexpr uint8_t kMulticastPrefixLength =
        128; ///< Multicast prefix length used to notify internal address changes.
};

/*
 * Certain fixed multicast addresses are defined as a set of chained (linked-list) constant `otNetifMulticastAddress`
 * entries:
 *
 * LinkLocalAllRouters -> RealmLocalAllRouters -> LinkLocalAll -> RealmLocalAll -> RealmLocalAllMplForwarders.
 *
 * All or a portion of the chain is appended to the end of `mMulticastAddresses` linked-list. If the interface is
 * subscribed to all-routers multicast addresses (using `SubscribeAllRoutersMulticast()`) then all the five entries
 * are appended. Otherwise only the last three are appended.
 *
 */

// "ff03::fc"
const otNetifMulticastAddress Netif::kRealmLocalAllMplForwardersMulticastAddress = {
    {{{0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc}}},
    nullptr};

// "ff03::01"
const otNetifMulticastAddress Netif::kRealmLocalAllNodesMulticastAddress = {
    {{{0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}},
    &Netif::kRealmLocalAllMplForwardersMulticastAddress};

// "ff02::01"
const otNetifMulticastAddress Netif::kLinkLocalAllNodesMulticastAddress = {
    {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}},
    &Netif::kRealmLocalAllNodesMulticastAddress};

// "ff03::02"
const otNetifMulticastAddress Netif::kRealmLocalAllRoutersMulticastAddress = {
    {{{0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}},
    &Netif::kLinkLocalAllNodesMulticastAddress};

// "ff02::02"
const otNetifMulticastAddress Netif::kLinkLocalAllRoutersMulticastAddress = {
    {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}},
    &Netif::kRealmLocalAllRoutersMulticastAddress};

//---------------------------------------------------------------------------------------------------------------------
// Netif

Netif::Netif(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mMulticastPromiscuous(false)
    , mAddressCallback(nullptr)
    , mAddressCallbackContext(nullptr)
{
}

bool Netif::IsMulticastSubscribed(const Address &aAddress) const
{
    return mMulticastAddresses.ContainsMatching(aAddress);
}

void Netif::SubscribeAllNodesMulticast(void)
{
    MulticastAddress *tail;
    MulticastAddress &linkLocalAllNodesAddress = AsCoreType(&AsNonConst(kLinkLocalAllNodesMulticastAddress));

    VerifyOrExit(!mMulticastAddresses.Contains(linkLocalAllNodesAddress));

    // Append the fixed chain of three multicast addresses to the
    // tail of the list:
    //
    //    LinkLocalAll -> RealmLocalAll -> RealmLocalAllMpl.

    tail = mMulticastAddresses.GetTail();

    if (tail == nullptr)
    {
        mMulticastAddresses.SetHead(&linkLocalAllNodesAddress);
    }
    else
    {
        tail->SetNext(&linkLocalAllNodesAddress);
    }

    Get<Notifier>().Signal(kEventIp6MulticastSubscribed);

#if !OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    VerifyOrExit(mAddressCallback != nullptr);
#endif

    for (const MulticastAddress *entry = &linkLocalAllNodesAddress; entry; entry = entry->GetNext())
    {
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
        Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressAdded, *entry, kOriginThread);

        if (mAddressCallback != nullptr)
#endif
        {
            AddressInfo addressInfo(*entry);

            mAddressCallback(&addressInfo, kAddressAdded, mAddressCallbackContext);
        }
    }

exit:
    return;
}

void Netif::UnsubscribeAllNodesMulticast(void)
{
    MulticastAddress *      prev;
    const MulticastAddress &linkLocalAllNodesAddress = AsCoreType(&AsNonConst(kLinkLocalAllNodesMulticastAddress));

    // The tail of multicast address linked list contains the
    // fixed addresses. Search if LinkLocalAll is present
    // in the list and find entry before it.
    //
    //    LinkLocalAll -> RealmLocalAll -> RealmLocalAllMpl.

    SuccessOrExit(mMulticastAddresses.Find(linkLocalAllNodesAddress, prev));

    // This method MUST be called after `UnsubscribeAllRoutersMulticast().
    // Verify this by checking the chain at the end of the list only
    // contains three entries and not the five fixed addresses (check that
    // `prev` entry before `LinkLocalAll` is not `RealmLocalRouters`):
    //
    //    LinkLocalAllRouters -> RealmLocalAllRouters -> LinkLocalAll
    //         -> RealmLocalAll -> RealmLocalAllMpl.

    OT_ASSERT(prev != AsCoreTypePtr(AsNonConst(&kRealmLocalAllRoutersMulticastAddress)));

    if (prev == nullptr)
    {
        mMulticastAddresses.Clear();
    }
    else
    {
        prev->SetNext(nullptr);
    }

    Get<Notifier>().Signal(kEventIp6MulticastUnsubscribed);

#if !OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    VerifyOrExit(mAddressCallback != nullptr);
#endif

    for (const MulticastAddress *entry = &linkLocalAllNodesAddress; entry; entry = entry->GetNext())
    {
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
        Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressRemoved, *entry, kOriginThread);

        if (mAddressCallback != nullptr)
#endif
        {
            AddressInfo addressInfo(*entry);

            mAddressCallback(&addressInfo, kAddressRemoved, mAddressCallbackContext);
        }
    }

exit:
    return;
}

void Netif::SubscribeAllRoutersMulticast(void)
{
    MulticastAddress *prev                        = nullptr;
    MulticastAddress &linkLocalAllRoutersAddress  = AsCoreType(&AsNonConst(kLinkLocalAllRoutersMulticastAddress));
    MulticastAddress &linkLocalAllNodesAddress    = AsCoreType(&AsNonConst(kLinkLocalAllNodesMulticastAddress));
    MulticastAddress &realmLocalAllRoutersAddress = AsCoreType(&AsNonConst(kRealmLocalAllRoutersMulticastAddress));

    // This method MUST be called after `SubscribeAllNodesMulticast()`
    // Ensure that the `LinkLocalAll` was found on the list.

    SuccessOrAssert(mMulticastAddresses.Find(linkLocalAllNodesAddress, prev));

    // The tail of multicast address linked list contains the
    // fixed addresses. We either have a chain of five addresses
    //
    //    LinkLocalAllRouters -> RealmLocalAllRouters ->
    //        LinkLocalAll -> RealmLocalAll -> RealmLocalAllMpl.
    //
    // or just the last three addresses
    //
    //    LinkLocalAll -> RealmLocalAll -> RealmLocalAllMpl.
    //
    // If the previous entry behind `LinkLocalAll` is
    // `RealmLocalAllRouters` then all five addresses are on
    // the list already.

    VerifyOrExit(prev != &realmLocalAllRoutersAddress);

    if (prev == nullptr)
    {
        mMulticastAddresses.SetHead(&linkLocalAllRoutersAddress);
    }
    else
    {
        prev->SetNext(&linkLocalAllRoutersAddress);
    }

    Get<Notifier>().Signal(kEventIp6MulticastSubscribed);

#if !OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    VerifyOrExit(mAddressCallback != nullptr);
#endif

    for (const MulticastAddress *entry = &linkLocalAllRoutersAddress; entry != &linkLocalAllNodesAddress;
         entry                         = entry->GetNext())
    {
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
        Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressAdded, *entry, kOriginThread);

        if (mAddressCallback != nullptr)
#endif
        {
            AddressInfo addressInfo(*entry);

            mAddressCallback(&addressInfo, kAddressAdded, mAddressCallbackContext);
        }
    }

exit:
    return;
}

void Netif::UnsubscribeAllRoutersMulticast(void)
{
    MulticastAddress *prev;
    MulticastAddress &linkLocalAllRoutersAddress = AsCoreType(&AsNonConst(kLinkLocalAllRoutersMulticastAddress));
    MulticastAddress &linkLocalAllNodesAddress   = AsCoreType(&AsNonConst(kLinkLocalAllNodesMulticastAddress));

    // The tail of multicast address linked list contains the
    // fixed addresses. We check for the chain of five addresses:
    //
    //    LinkLocalAllRouters -> RealmLocalAllRouters ->
    //        LinkLocalAll -> RealmLocalAll -> RealmLocalAllMpl.
    //
    // If found, we then replace the entry behind `LinkLocalAllRouters`
    // to point to `LinkLocalAll` instead (so that tail contains the
    // three fixed addresses at end of the chain).

    SuccessOrExit(mMulticastAddresses.Find(linkLocalAllRoutersAddress, prev));

    if (prev == nullptr)
    {
        mMulticastAddresses.SetHead(&linkLocalAllNodesAddress);
    }
    else
    {
        prev->SetNext(&linkLocalAllNodesAddress);
    }

    Get<Notifier>().Signal(kEventIp6MulticastUnsubscribed);

#if !OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    VerifyOrExit(mAddressCallback != nullptr);
#endif

    for (const MulticastAddress *entry = &linkLocalAllRoutersAddress; entry != &linkLocalAllNodesAddress;
         entry                         = entry->GetNext())
    {
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
        Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressRemoved, *entry, kOriginThread);

        if (mAddressCallback != nullptr)
#endif
        {
            AddressInfo addressInfo(*entry);

            mAddressCallback(&addressInfo, kAddressRemoved, mAddressCallbackContext);
        }
    }

exit:
    return;
}

bool Netif::IsMulticastAddressExternal(const MulticastAddress &aAddress) const
{
    return mExtMulticastAddressPool.IsPoolEntry(static_cast<const ExternalMulticastAddress &>(aAddress));
}

void Netif::SubscribeMulticast(MulticastAddress &aAddress)
{
    SuccessOrExit(mMulticastAddresses.Add(aAddress));

    Get<Notifier>().Signal(kEventIp6MulticastSubscribed);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressAdded, aAddress, kOriginThread);
#endif

    if (mAddressCallback != nullptr)
    {
        AddressInfo addressInfo(aAddress);

        mAddressCallback(&addressInfo, kAddressAdded, mAddressCallbackContext);
    }

exit:
    return;
}

void Netif::UnsubscribeMulticast(const MulticastAddress &aAddress)
{
    SuccessOrExit(mMulticastAddresses.Remove(aAddress));

    Get<Notifier>().Signal(kEventIp6MulticastUnsubscribed);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressRemoved, aAddress, kOriginThread);
#endif

    if (mAddressCallback != nullptr)
    {
        AddressInfo addressInfo(aAddress);

        mAddressCallback(&addressInfo, kAddressRemoved, mAddressCallbackContext);
    }

exit:
    return;
}

Error Netif::SubscribeExternalMulticast(const Address &aAddress)
{
    Error             error                      = kErrorNone;
    MulticastAddress &linkLocalAllRoutersAddress = AsCoreType(&AsNonConst(kLinkLocalAllRoutersMulticastAddress));
    ExternalMulticastAddress *entry;

    VerifyOrExit(aAddress.IsMulticast(), error = kErrorInvalidArgs);
    VerifyOrExit(!IsMulticastSubscribed(aAddress), error = kErrorAlready);

    // Check that the address is not one of the fixed addresses:
    // LinkLocalAllRouters -> RealmLocalAllRouters -> LinkLocalAllNodes
    // -> RealmLocalAllNodes -> RealmLocalAllMpl.

    for (const MulticastAddress *cur = &linkLocalAllRoutersAddress; cur; cur = cur->GetNext())
    {
        VerifyOrExit(cur->GetAddress() != aAddress, error = kErrorRejected);
    }

    entry = mExtMulticastAddressPool.Allocate();
    VerifyOrExit(entry != nullptr, error = kErrorNoBufs);

    entry->mAddress = aAddress;
#if OPENTHREAD_CONFIG_MLR_ENABLE
    entry->mMlrState = kMlrStateToRegister;
#endif
    mMulticastAddresses.Push(*entry);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressAdded, *entry, kOriginManual);
#endif

    Get<Notifier>().Signal(kEventIp6MulticastSubscribed);

exit:
    return error;
}

Error Netif::UnsubscribeExternalMulticast(const Address &aAddress)
{
    Error             error = kErrorNone;
    MulticastAddress *entry;
    MulticastAddress *prev;

    entry = mMulticastAddresses.FindMatching(aAddress, prev);
    VerifyOrExit(entry != nullptr, error = kErrorNotFound);

    VerifyOrExit(IsMulticastAddressExternal(*entry), error = kErrorRejected);

    mMulticastAddresses.PopAfter(prev);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressRemoved, *entry, kOriginManual);
#endif

    mExtMulticastAddressPool.Free(static_cast<ExternalMulticastAddress &>(*entry));

    Get<Notifier>().Signal(kEventIp6MulticastUnsubscribed);

exit:
    return error;
}

void Netif::UnsubscribeAllExternalMulticastAddresses(void)
{
    MulticastAddress *next;

    for (MulticastAddress *entry = mMulticastAddresses.GetHead(); entry != nullptr; entry = next)
    {
        next = entry->GetNext();

        if (IsMulticastAddressExternal(*entry))
        {
            IgnoreError(UnsubscribeExternalMulticast(entry->GetAddress()));
        }
    }
}

void Netif::SetAddressCallback(otIp6AddressCallback aCallback, void *aCallbackContext)
{
    mAddressCallback        = aCallback;
    mAddressCallbackContext = aCallbackContext;
}

void Netif::AddUnicastAddress(UnicastAddress &aAddress)
{
    SuccessOrExit(mUnicastAddresses.Add(aAddress));

    Get<Notifier>().Signal(aAddress.mRloc ? kEventThreadRlocAdded : kEventIp6AddressAdded);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressAdded, aAddress);
#endif

    if (mAddressCallback != nullptr)
    {
        AddressInfo addressInfo(aAddress);

        mAddressCallback(&addressInfo, kAddressAdded, mAddressCallbackContext);
    }

exit:
    return;
}

void Netif::RemoveUnicastAddress(const UnicastAddress &aAddress)
{
    SuccessOrExit(mUnicastAddresses.Remove(aAddress));

    Get<Notifier>().Signal(aAddress.mRloc ? kEventThreadRlocRemoved : kEventIp6AddressRemoved);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressRemoved, aAddress);
#endif

    if (mAddressCallback != nullptr)
    {
        AddressInfo addressInfo(aAddress);

        mAddressCallback(&addressInfo, kAddressRemoved, mAddressCallbackContext);
    }

exit:
    return;
}

Error Netif::AddExternalUnicastAddress(const UnicastAddress &aAddress)
{
    Error           error = kErrorNone;
    UnicastAddress *entry;

    VerifyOrExit(!aAddress.GetAddress().IsMulticast(), error = kErrorInvalidArgs);

    entry = mUnicastAddresses.FindMatching(aAddress.GetAddress());

    if (entry != nullptr)
    {
        VerifyOrExit(IsUnicastAddressExternal(*entry), error = kErrorAlready);

        entry->mPrefixLength  = aAddress.mPrefixLength;
        entry->mAddressOrigin = aAddress.mAddressOrigin;
        entry->mPreferred     = aAddress.mPreferred;
        entry->mValid         = aAddress.mValid;
        ExitNow();
    }

    VerifyOrExit(!aAddress.GetAddress().IsLinkLocal(), error = kErrorInvalidArgs);

    entry = mExtUnicastAddressPool.Allocate();
    VerifyOrExit(entry != nullptr, error = kErrorNoBufs);

    *entry = aAddress;
    mUnicastAddresses.Push(*entry);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressAdded, *entry);
#endif

    Get<Notifier>().Signal(kEventIp6AddressAdded);

exit:
    return error;
}

Error Netif::RemoveExternalUnicastAddress(const Address &aAddress)
{
    Error           error = kErrorNone;
    UnicastAddress *entry;
    UnicastAddress *prev;

    entry = mUnicastAddresses.FindMatching(aAddress, prev);
    VerifyOrExit(entry != nullptr, error = kErrorNotFound);

    VerifyOrExit(IsUnicastAddressExternal(*entry), error = kErrorRejected);

    mUnicastAddresses.PopAfter(prev);

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    Get<Utils::HistoryTracker>().RecordAddressEvent(kAddressRemoved, *entry);
#endif

    mExtUnicastAddressPool.Free(*entry);
    Get<Notifier>().Signal(kEventIp6AddressRemoved);

exit:
    return error;
}

void Netif::RemoveAllExternalUnicastAddresses(void)
{
    UnicastAddress *next;

    for (UnicastAddress *entry = mUnicastAddresses.GetHead(); entry != nullptr; entry = next)
    {
        next = entry->GetNext();

        if (IsUnicastAddressExternal(*entry))
        {
            IgnoreError(RemoveExternalUnicastAddress(entry->GetAddress()));
        }
    }
}

bool Netif::HasUnicastAddress(const Address &aAddress) const
{
    return mUnicastAddresses.ContainsMatching(aAddress);
}

bool Netif::IsUnicastAddressExternal(const UnicastAddress &aAddress) const
{
    return mExtUnicastAddressPool.IsPoolEntry(aAddress);
}

//---------------------------------------------------------------------------------------------------------------------
// Netif::UnicastAddress

void Netif::UnicastAddress::InitAsThreadOrigin(bool aPreferred)
{
    Clear();
    mPrefixLength  = NetworkPrefix::kLength;
    mAddressOrigin = kOriginThread;
    mPreferred     = aPreferred;
    mValid         = true;
}

void Netif::UnicastAddress::InitAsThreadOriginRealmLocalScope(void)
{
    InitAsThreadOrigin();
    SetScopeOverride(Address::kRealmLocalScope);
}

void Netif::UnicastAddress::InitAsThreadOriginGlobalScope(void)
{
    Clear();
    mAddressOrigin = kOriginThread;
    mValid         = true;
    SetScopeOverride(Address::kGlobalScope);
}

void Netif::UnicastAddress::InitAsSlaacOrigin(uint8_t aPrefixLength, bool aPreferred)
{
    Clear();
    mPrefixLength  = aPrefixLength;
    mAddressOrigin = kOriginSlaac;
    mPreferred     = aPreferred;
    mValid         = true;
}

//---------------------------------------------------------------------------------------------------------------------
// Netif::ExternalMulticastAddress::Iterator

Netif::ExternalMulticastAddress::Iterator::Iterator(const Netif &aNetif, Address::TypeFilter aFilter)
    : ItemPtrIterator(nullptr)
    , mNetif(aNetif)
    , mFilter(aFilter)
{
    AdvanceFrom(mNetif.GetMulticastAddresses().GetHead());
}

void Netif::ExternalMulticastAddress::Iterator::AdvanceFrom(const MulticastAddress *aAddr)
{
    while (aAddr != nullptr &&
           !(mNetif.IsMulticastAddressExternal(*aAddr) && aAddr->GetAddress().MatchesFilter(mFilter)))
    {
        aAddr = aAddr->GetNext();
    }

    mItem = AsNonConst(static_cast<const ExternalMulticastAddress *>(aAddr));
}

} // namespace Ip6
} // namespace ot
