/*
 *  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 includes definitions related to Thread Network Data service/server entries.
 */

#ifndef NETWORK_DATA_SERVICE_HPP_
#define NETWORK_DATA_SERVICE_HPP_

#include "openthread-core-config.h"

#include <openthread/netdata.h>

#include "backbone_router/bbr_leader.hpp"
#include "common/encoding.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/serial_number.hpp"
#include "net/socket.hpp"
#include "thread/network_data_tlvs.hpp"

namespace ot {
namespace NetworkData {
namespace Service {

using ot::Encoding::BigEndian::HostSwap16;
using ot::Encoding::BigEndian::HostSwap32;

const uint32_t kThreadEnterpriseNumber = ServiceTlv::kThreadEnterpriseNumber; ///< Thread enterprise number.

#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)

/**
 * This type implements Thread Network Data "Backbone Router Service" server data generation and parsing.
 *
 */
class BackboneRouter
{
public:
    /**
     * This constant variable represents the Backbone Router service data.
     *
     * The service data contains only the service number (THREAD_SERVICE_DATA_BBR) as a single byte.
     *
     */
    static const uint8_t     kServiceData        = 0x01;
    static constexpr uint8_t kServiceDataMinSize = 1;

    /**
     * This class implements the generation and parsing of "Backbone Router Service" server data.
     *
     */
    OT_TOOL_PACKED_BEGIN
    class ServerData
    {
    public:
        /**
         * This method returns the length (in bytes) of server data.
         *
         * @returns The server data length in bytes.
         *
         */
        uint8_t GetLength(void) const { return sizeof(ServerData); }

        /**
         * This method returns the sequence number of Backbone Router.
         *
         * @returns  The sequence number of the Backbone Router.
         *
         */
        uint8_t GetSequenceNumber(void) const { return mSequenceNumber; }

        /**
         * This method sets the sequence number of Backbone Router.
         *
         * @param[in]  aSequenceNumber  The sequence number of Backbone Router.
         *
         */
        void SetSequenceNumber(uint8_t aSequenceNumber) { mSequenceNumber = aSequenceNumber; }

        /**
         * This method returns the Registration Delay (in seconds) of Backbone Router.
         *
         * @returns The BBR Registration Delay (in seconds) of Backbone Router.
         *
         */
        uint16_t GetReregistrationDelay(void) const { return HostSwap16(mReregistrationDelay); }

        /**
         * This method sets the Registration Delay (in seconds) of Backbone Router.
         *
         * @param[in]  aReregistrationDelay  The Registration Delay (in seconds) of Backbone Router.
         *
         */
        void SetReregistrationDelay(uint16_t aReregistrationDelay)
        {
            mReregistrationDelay = HostSwap16(aReregistrationDelay);
        }

        /**
         * This method returns the multicast listener report timeout (in seconds) of Backbone Router.
         *
         * @returns The multicast listener report timeout (in seconds) of Backbone Router.
         *
         */
        uint32_t GetMlrTimeout(void) const { return HostSwap32(mMlrTimeout); }

        /**
         * This method sets multicast listener report timeout (in seconds) of Backbone Router.
         *
         * @param[in]  aMlrTimeout  The multicast listener report timeout (in seconds) of Backbone Router.
         *
         */
        void SetMlrTimeout(uint32_t aMlrTimeout) { mMlrTimeout = HostSwap32(aMlrTimeout); }

    private:
        uint8_t  mSequenceNumber;
        uint16_t mReregistrationDelay;
        uint32_t mMlrTimeout;
    } OT_TOOL_PACKED_END;

    BackboneRouter(void) = delete;
};

#endif // #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)

/**
 * This type implements Thread Network Data "DNS/SRP Service Anycast Address" generation and parsing.
 *
 */
class DnsSrpAnycast
{
public:
    static constexpr uint8_t kServiceNumber = 0x5c; ///< The service number of a `DnsSrpAnycast` entry.

    /**
     * This constant variable represents the short version of service data.
     *
     * The short version of service data contains only service number as a single byte.
     *
     */
    static const uint8_t kServiceData = kServiceNumber;

    /**
     * This structure represents information about an DNS/SRP server parsed from related Network Data service entries.
     *
     */
    struct Info
    {
        Ip6::Address mAnycastAddress; ///< The anycast address associated with the DNS/SRP servers.
        uint8_t      mSequenceNumber; ///< Sequence number used to notify SRP client if they need to re-register.
    };

    /**
     * This class represents the "DNS/SRP Service (Anycast)" service data.
     *
     */
    OT_TOOL_PACKED_BEGIN
    class ServiceData
    {
    public:
        /**
         * This constructor initializes the `ServiceData` object.
         *
         * @param[in] aSequenceNumber   The sequence number of "DNS/SRP server" service.
         *
         */
        explicit ServiceData(uint8_t aSequenceNumber)
            : mServiceNumber(kServiceNumber)
            , mSequenceNumber(aSequenceNumber)
        {
            OT_UNUSED_VARIABLE(mServiceNumber);
        }

        /**
         * This method returns the length (in bytes) of service data.
         *
         * @returns The data length in bytes.
         *
         */
        uint8_t GetLength(void) const { return sizeof(ServiceData); }

        /**
         * This method returns the sequence number.
         *
         * @returns The sequence number.
         *
         */
        uint8_t GetSequenceNumber(void) const { return mSequenceNumber; }

    private:
        uint8_t mServiceNumber;
        uint8_t mSequenceNumber;
    } OT_TOOL_PACKED_END;

    DnsSrpAnycast(void) = delete;
};

/**
 * This type implements Thread Network Data DNS/SRP Service (Unicast Address) generation and parsing.
 *
 */
class DnsSrpUnicast
{
public:
    static constexpr uint8_t kServiceNumber = 0x5d; ///< The service number of `DnsSrpUnicast` entry.

    /**
     * This constant variable represents the short version of service data.
     *
     * The short version of service data contains only service number as a single byte.
     *
     */
    static const uint8_t kServiceData = kServiceNumber;

    /**
     * This enumeration represents the origin a `DnsSrpUnicast` entry.
     *
     */
    enum Origin : uint8_t
    {
        kFromServiceData, ///< Socket address is from service data.
        kFromServerData,  ///< Socket address is from server data.
    };

    /**
     * This structure represents information about an DNS/SRP server parsed from related Network Data service entries.
     *
     */
    struct Info
    {
        Ip6::SockAddr mSockAddr; ///< The socket address (IPv6 address and port) of the DNS/SRP server.
        Origin        mOrigin;   ///< The origin of the socket address (whether from service or server data).
    };

    /**
     * This class represents long version of "DNS/SRP Service (Unicast)" service data.
     *
     */
    OT_TOOL_PACKED_BEGIN
    class ServiceData
    {
    public:
        /**
         * This constructor initializes the `ServiceData` object.
         *
         * @param[in] aAddress   The IPv6 address of DNS/SRP server.
         * @param[in] aPort      The port number of DNS/SRP server.
         *
         */
        explicit ServiceData(const Ip6::Address &aAddress, uint16_t aPort)
            : mServiceNumber(kServiceNumber)
            , mAddress(aAddress)
            , mPort(HostSwap16(aPort))
        {
            OT_UNUSED_VARIABLE(mServiceNumber);
        }

        /**
         * This method returns the length (in bytes) of service data.
         *
         * @returns The data length in bytes.
         *
         */
        uint8_t GetLength(void) const { return sizeof(ServiceData); }

        /**
         * This method returns the IPv6 address.
         *
         * @returns The IPv6 address
         *
         */
        const Ip6::Address &GetAddress(void) const { return mAddress; }

        /**
         * This method returns the port number.
         *
         * @returns The port number.
         *
         */
        uint16_t GetPort(void) const { return HostSwap16(mPort); }

    private:
        uint8_t      mServiceNumber;
        Ip6::Address mAddress;
        uint16_t     mPort;
    } OT_TOOL_PACKED_END;

    /**
     * This class represents long version of "DNS/SRP Service (Unicast)" server data.
     *
     */
    OT_TOOL_PACKED_BEGIN
    class ServerData
    {
    public:
        /**
         * This constructor initializes the `ServerData` object.
         *
         * @param[in] aAddress   The IPv6 address of DNS/SRP server.
         * @param[in] aPort      The port number of DNS/SRP server.
         *
         */
        ServerData(const Ip6::Address &aAddress, uint16_t aPort)
            : mAddress(aAddress)
            , mPort(HostSwap16(aPort))
        {
        }

        /**
         * This method returns the length (in bytes) of server data.
         *
         * @returns The data length in bytes.
         *
         */
        uint8_t GetLength(void) const { return sizeof(ServerData); }

        /**
         * This method returns the IPv6 address.
         *
         * @returns The IPv6 address
         *
         */
        const Ip6::Address &GetAddress(void) const { return mAddress; }

        /**
         * This method returns the port number.
         *
         * @returns The port number.
         *
         */
        uint16_t GetPort(void) const { return HostSwap16(mPort); }

    private:
        Ip6::Address mAddress;
        uint16_t     mPort;
    } OT_TOOL_PACKED_END;

    DnsSrpUnicast(void) = delete;
};

/**
 * This class manages the Thread Service entries in Thread Network Data.
 *
 */
class Manager : public InstanceLocator, private NonCopyable
{
public:
    /**
     * This class represents an iterator used to iterate through Network Data Service entries.
     *
     */
    class Iterator : public Clearable<Iterator>
    {
        friend class Manager;

    public:
        /**
         * This constructor initializes the iterator (as empty/clear).
         *
         */
        Iterator(void)
            : mServiceTlv(nullptr)
            , mServerSubTlv(nullptr)
        {
        }

        /**
         * This method resets the iterator to start from beginning.
         *
         */
        void Reset(void)
        {
            mServiceTlv   = nullptr;
            mServerSubTlv = nullptr;
        }

    private:
        const ServiceTlv *mServiceTlv;
        const ServerTlv * mServerSubTlv;
    };

    /**
     * This constructor initializes the `Manager` object.
     *
     * @param[in]  aInstance     A reference to the OpenThread instance.
     *
     */
    explicit Manager(Instance &aInstance)
        : InstanceLocator(aInstance)
    {
    }

#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
    /**
     * This method adds a Thread Service entry to the local Thread Network Data.
     *
     * This version of `Add<SeviceType>()` is intended for use with a `ServiceType` that has a constant service data
     * format with a non-empty and potentially non-const server data format (provided as input parameter).
     *
     * The template type `ServiceType` has the following requirements:
     *   - It MUST have a constant variable `ServiceType::kServiceData` specifying the service data.
     *   - It MUST define nested type `ServiceType::ServerData` representing the server data (and its format).
     *   - The `ServiceType::ServerData` MUST provide `GetLength()` method returning the length of server data.
     *
     * @tparam    ServiceType    The service type to be added.
     *
     * @param[in] aServerData    The server data.
     * @param[in] aServerStable  The Stable flag value for Server TLV.
     *
     * @retval kErrorNone     Successfully added the Service entry.
     * @retval kErrorNoBufs   Insufficient space to add the Service entry.
     *
     */
    template <typename ServiceType>
    Error Add(const typename ServiceType::ServerData &aServerData, bool aServerStable = true)
    {
        return AddService(&ServiceType::kServiceData, sizeof(ServiceType::kServiceData), aServerStable, &aServerData,
                          aServerData.GetLength());
    }

    /**
     * This method adds a Thread Service entry to the local Thread Network Data.
     *
     * This version of `Add<SeviceType>()` is intended for use with a `ServiceType` that has a non-const service data
     * format (provided as input parameter) with an empty server data.
     *
     * The template type `ServiceType` has the following requirements:
     *   - It MUST define nested type `ServiceType::ServiceData` representing the service data (and its format).
     *   - The `ServiceType::ServiceData` MUST provide `GetLength()` method returning the length of service data.
     *
     * @tparam    ServiceType    The service type to be added.
     *
     * @param[in] aServiceData   The service data.
     * @param[in] aServerStable  The Stable flag value for Server TLV.
     *
     * @retval kErrorNone     Successfully added the Service entry.
     * @retval kErrorNoBufs   Insufficient space to add the Service entry.
     *
     */
    template <typename ServiceType>
    Error Add(const typename ServiceType::ServiceData &aServiceData, bool aServerStable = true)
    {
        return AddService(&aServiceData, aServiceData.GetLength(), aServerStable, nullptr, 0);
    }

    /**
     * This method removes a Thread Service entry from the local Thread Network Data.
     *
     * This version of `Remove<SeviceType>()` is intended for use with a `ServiceType` that has a constant service data
     * format.
     *
     * The template type `ServiceType` has the following requirements:
     *   - It MUST have a constant variable `ServiceType::kServiceData` specifying the service data.
     *
     * @tparam   ServiceType       The service type to be removed.
     *
     * @retval kErrorNone       Successfully removed the Service entry.
     * @retval kErrorNotFound   Could not find the Service entry.
     *
     */
    template <typename ServiceType> Error Remove(void)
    {
        return RemoveService(&ServiceType::kServiceData, sizeof(ServiceType::kServiceData));
    }

    /**
     * This method removes a Thread Service entry from the local Thread Network Data.
     *
     * This version of `Remove<SeviceType>()` is intended for use with a `ServiceType` that has a non-const service data
     * format (provided as input parameter).
     *
     * The template type `ServiceType` has the following requirements:
     *   - It MUST define nested type `ServiceType::ServiceData` representing the service data (and its format).
     *   - The `ServiceType::ServiceData` MUST provide `GetLength()` method returning the length of service data.
     *
     * @tparam   ServiceType       The service type to be removed.
     *
     * @param[in] aServiceData     The service data.
     *
     * @retval kErrorNone       Successfully removed the Service entry.
     * @retval kErrorNotFound   Could not find the Service entry.
     *
     */
    template <typename ServiceType> Error Remove(const typename ServiceType::ServiceData &aServiceData)
    {
        return RemoveService(&aServiceData, aServiceData.GetLength());
    }

#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE

    /**
     * This method gets the Service ID for the specified service from Thread Network Data.
     *
     * The template type `ServiceType` has the following requirements:
     *   - It MUST have a constant `ServiceType::kServiceNumber` specifying the service number.
     *
     * @tparam     ServiceType     The service type to be added.
     *
     * @param[in]  aServerStable   The Stable flag value for Server TLV
     * @param[out] aServiceId      A reference where to put the Service ID.
     *
     * @retval kErrorNone       Successfully got the Service ID.
     * @retval kErrorNotFound   The specified service was not found.
     *
     */
    template <typename ServiceType> Error GetServiceId(bool aServerStable, uint8_t &aServiceId) const
    {
        return GetServiceId(&ServiceType::kServiceData, sizeof(ServiceType::kServiceData), aServerStable, aServiceId);
    }

#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
    /**
     * This method gets the Primary Backbone Router (PBBR) in the Thread Network Data.
     *
     * @param[out]  aConfig      The Primary Backbone Router configuration.
     *
     */
    void GetBackboneRouterPrimary(ot::BackboneRouter::BackboneRouterConfig &aConfig) const;
#endif

    /**
     * This method gets the next DNS/SRP info from the Thread Network Data "DNS/SRP Service Anycast Address" entries.
     *
     * To get the first entry, @p aIterator should be cleared (e.g., a new instance of `Iterator` or calling `Clear()`
     * method).
     *
     * @param[in,out] aIterator    A reference to an iterator.
     * @param[out]    aInfo        A reference to `DnsSrpAnycast::Info` to return the info.
     *
     * @retval kErrorNone       Successfully got the next info. @p aInfo and @p aIterator are updated.
     * @retval kErrorNotFound   No more matching entries in the Network Data.
     *
     */
    Error GetNextDnsSrpAnycastInfo(Iterator &aIterator, DnsSrpAnycast::Info &aInfo) const;

    /**
     * This method finds the preferred DNS/SRP info among all the Thread Network Data "DNS/SRP Service Anycast Address"
     * entries.
     *
     * The preferred entry is determined based on the sequence number value where a larger value (in the sense
     * specified by Serial Number Arithmetic logic in RFC-1982) is considered more recent and therefore preferred.
     *
     * @param[out] aInfo        A reference to `DnsSrpAnycast::Info` to return the info.
     *
     * @retval kErrorNone       Successfully found the preferred info. @p aInfo is updated.
     * @retval kErrorNotFound   No "DNS/SRP Service Anycast" entry in Network Data.
     *
     */
    Error FindPreferredDnsSrpAnycastInfo(DnsSrpAnycast::Info &aInfo) const;

    /**
     * This method gets the next DNS/SRP info from the Thread Network Data "DNS/SRP Service Unicast Address" entries.
     *
     * To get the first entry @p aIterator should be cleared (e.g., a new instance of `Iterator` or calling `Clear()`
     * method).
     *
     * @param[in,out] aIterator    A reference to an iterator.
     * @param[out]    aInfo        A reference to `DnsSrpUnicast::Info` to return the info.
     *
     * @retval kErrorNone       Successfully got the next info. @p aInfo and @p aIterator are updated.
     * @retval kErrorNotFound   No more matching entries in the Network Data.
     *
     */
    Error GetNextDnsSrpUnicastInfo(Iterator &aIterator, DnsSrpUnicast::Info &aInfo) const;

private:
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
    Error AddService(const void *aServiceData,
                     uint8_t     aServiceDataLength,
                     bool        aServerStable,
                     const void *aServerData,
                     uint8_t     aServerDataLength);
    Error RemoveService(const void *aServiceData, uint8_t aServiceDataLength);
#endif

    Error GetServiceId(const void *aServiceData,
                       uint8_t     aServiceDataLength,
                       bool        aServerStable,
                       uint8_t &   aServiceId) const;
    Error IterateToNextServer(Iterator &aIterator) const;

#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
    bool IsBackboneRouterPreferredTo(const ServerTlv &                 aServerTlv,
                                     const BackboneRouter::ServerData &aServerData,
                                     const ServerTlv &                 aOtherServerTlv,
                                     const BackboneRouter::ServerData &aOtherServerData) const;
#endif
};

} // namespace Service
} // namespace NetworkData
} // namespace ot

#endif // NETWORK_DATA_SERVICE_HPP_
