/*
 *  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 includes definitions for UDP/IPv6 sockets.
 */

#ifndef OT_CORE_NET_UDP6_HPP_
#define OT_CORE_NET_UDP6_HPP_

#include "openthread-core-config.h"

#include <openthread/udp.h>
#include <openthread/platform/udp.h>

#include "common/as_core_type.hpp"
#include "common/callback.hpp"
#include "common/clearable.hpp"
#include "common/linked_list.hpp"
#include "common/locator.hpp"
#include "common/message_allocator.hpp"
#include "common/non_copyable.hpp"
#include "net/ip6_headers.hpp"

namespace ot {
namespace Ip6 {

class Udp;

/**
 * @addtogroup core-udp
 *
 * @brief
 *   This module includes definitions for UDP/IPv6 sockets.
 *
 * @{
 */

#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE && OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
#error "OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE and OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE must not both be set."
#endif

/**
 * Defines the network interface identifiers.
 */
enum NetifIdentifier : uint8_t
{
    kNetifUnspecified    = OT_NETIF_UNSPECIFIED,     ///< Unspecified network interface.
    kNetifThreadHost     = OT_NETIF_THREAD_HOST,     ///< The host Thread interface - allow use of platform UDP.
    kNetifThreadInternal = OT_NETIF_THREAD_INTERNAL, ///< The internal Thread interface - do not use platform UDP.
    kNetifBackbone       = OT_NETIF_BACKBONE,        ///< The Backbone interface.
};

/**
 * Implements core UDP message handling.
 */
class Udp : public InstanceLocator, public MessageAllocator<Udp, ReservedHeaderSize::kUdpMessage>, private NonCopyable
{
public:
    typedef UdpHeader    Header;         ///< UDP header.
    typedef otUdpReceive ReceiveHandler; ///< Receive handler callback.

    /**
     * Implements a UDP/IPv6 socket.
     */
    class SocketHandle : public otUdpSocket, public LinkedListEntry<SocketHandle>, public Clearable<SocketHandle>
    {
        friend class Udp;
        friend class LinkedList<SocketHandle>;

    public:
        /**
         * Indicates whether or not the socket is bound.
         *
         * @retval TRUE if the socket is bound (i.e. source port is non-zero).
         * @retval FALSE if the socket is not bound (source port is zero).
         */
        bool IsBound(void) const { return mSockName.mPort != 0; }

        /**
         * Returns the local socket address.
         *
         * @returns A reference to the local socket address.
         */
        SockAddr &GetSockName(void) { return AsCoreType(&mSockName); }

        /**
         * Returns the local socket address.
         *
         * @returns A reference to the local socket address.
         */
        const SockAddr &GetSockName(void) const { return AsCoreType(&mSockName); }

        /**
         * Returns the peer's socket address.
         *
         * @returns A reference to the peer's socket address.
         */
        SockAddr &GetPeerName(void) { return AsCoreType(&mPeerName); }

        /**
         * Returns the peer's socket address.
         *
         * @returns A reference to the peer's socket address.
         */
        const SockAddr &GetPeerName(void) const { return AsCoreType(&mPeerName); }

        /**
         * Returns the network interface identifier.
         *
         * @returns The network interface identifier.
         */
        NetifIdentifier GetNetifId(void) const { return static_cast<NetifIdentifier>(mNetifId); }

        /**
         * Sets the network interface identifier.
         *
         * @param[in] aNetifId   The network interface identifier.
         */
        void SetNetifId(NetifIdentifier aNetifId) { mNetifId = static_cast<otNetifIdentifier>(aNetifId); }

        /**
         * Indicates whether or not the socket can use platform UDP.
         *
         * @retval TRUE    This socket should use platform UDP.
         * @retval FALSE   This socket is associated with the internal Thread interface and should not use platform UDP.
         */
        bool ShouldUsePlatformUdp(void) const { return GetNetifId() != kNetifThreadInternal; }

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
        /**
         * Indicate whether or not the socket is bound to the backbone network interface.
         *
         * @retval TRUE    This is a backbone socket.
         * @retval FALSE   This is not a backbone socket.
         */
        bool IsBackbone(void) const { return (GetNetifId() == kNetifBackbone); }
#endif

    private:
        bool Matches(uint16_t aSockPort) const { return GetSockName().GetPort() == aSockPort; }
        bool Matches(const MessageInfo &aMessageInfo) const;

        void HandleUdpReceive(Message &aMessage, const MessageInfo &aMessageInfo)
        {
            mHandler(mContext, &aMessage, &aMessageInfo);
        }
    };

    /**
     * Implements a UDP/IPv6 socket.
     */
    class Socket : public InstanceLocator,
                   public SocketHandle,
                   public MessageAllocator<Socket, ReservedHeaderSize::kUdpMessage>
    {
        friend class Udp;

    public:
        /**
         * Initializes the object.
         *
         * @param[in]  aInstance  A reference to OpenThread instance.
         * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
         * @param[in]  aContext  A pointer to arbitrary context information.
         */
        Socket(Instance &aInstance, ReceiveHandler aHandler, void *aContext);

        /**
         * Opens the UDP socket.
         *
         * @param[in]  aNetifId   The network interface identifier.
         *
         * @retval kErrorNone     Successfully opened the socket.
         * @retval kErrorFailed   Failed to open the socket.
         */
        Error Open(NetifIdentifier aNetifId);

        /**
         * Returns if the UDP socket is open.
         *
         * @returns If the UDP socket is open.
         */
        bool IsOpen(void) const;

        /**
         * Binds the UDP socket.
         *
         * @param[in]  aSockAddr         A reference to the socket address.
         *
         * @retval kErrorNone            Successfully bound the socket.
         * @retval kErrorInvalidArgs     Unable to bind to Thread network interface with the given address.
         * @retval kErrorFailed          Failed to bind UDP Socket.
         */
        Error Bind(const SockAddr &aSockAddr);

        /**
         * Binds the UDP socket.
         *
         * @param[in]  aPort             A port number.
         *
         * @retval kErrorNone            Successfully bound the socket.
         * @retval kErrorFailed          Failed to bind UDP Socket.
         */
        Error Bind(uint16_t aPort);

        /**
         * Binds the UDP socket.
         *
         * @retval kErrorNone    Successfully bound the socket.
         * @retval kErrorFailed  Failed to bind UDP Socket.
         */
        Error Bind(void) { return Bind(0); }

        /**
         * Connects the UDP socket.
         *
         * @param[in]  aSockAddr  A reference to the socket address.
         *
         * @retval kErrorNone    Successfully connected the socket.
         * @retval kErrorFailed  Failed to connect UDP Socket.
         */
        Error Connect(const SockAddr &aSockAddr);

        /**
         * Connects the UDP socket.
         *
         * @param[in]  aPort        A port number.
         *
         * @retval kErrorNone    Successfully connected the socket.
         * @retval kErrorFailed  Failed to connect UDP Socket.
         */
        Error Connect(uint16_t aPort);

        /**
         * Connects the UDP socket.
         *
         * @retval kErrorNone    Successfully connected the socket.
         * @retval kErrorFailed  Failed to connect UDP Socket.
         */
        Error Connect(void) { return Connect(0); }

        /**
         * Closes the UDP socket.
         *
         * @retval kErrorNone    Successfully closed the UDP socket.
         * @retval kErrorFailed  Failed to close UDP Socket.
         */
        Error Close(void);

        /**
         * Sends a UDP message.
         *
         * @param[in]  aMessage      The message to send.
         * @param[in]  aMessageInfo  The message info associated with @p aMessage.
         *
         * @retval kErrorNone         Successfully sent the UDP message.
         * @retval kErrorInvalidArgs  If no peer is specified in @p aMessageInfo or by Connect().
         * @retval kErrorNoBufs       Insufficient available buffer to add the UDP and IPv6 headers.
         */
        Error SendTo(Message &aMessage, const MessageInfo &aMessageInfo);

#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
        /**
         * Configures the UDP socket to join a multicast group on a Host network interface.
         *
         * @param[in]  aNetifIdentifier     The network interface identifier.
         * @param[in]  aAddress             The multicast group address.
         *
         * @retval  kErrorNone    Successfully joined the multicast group.
         * @retval  kErrorFailed  Failed to join the multicast group.
         */
        Error JoinNetifMulticastGroup(NetifIdentifier aNetifIdentifier, const Address &aAddress);

        /**
         * Configures the UDP socket to leave a multicast group on a Host network interface.
         *
         * @param[in]  aNetifIdentifier     The network interface identifier.
         * @param[in]  aAddress             The multicast group address.
         *
         * @retval  kErrorNone   Successfully left the multicast group.
         * @retval  kErrorFailed Failed to leave the multicast group.
         */
        Error LeaveNetifMulticastGroup(NetifIdentifier aNetifIdentifier, const Address &aAddress);
#endif
    };

    /**
     * A socket owned by a specific type with a given  owner type as the callback.
     *
     * @tparam Owner                The type of the owner of this socket.
     * @tparam HandleUdpReceivePtr  A pointer to a non-static member method of `Owner` to handle received messages.
     */
    template <typename Owner, void (Owner::*HandleUdpReceivePtr)(Message &aMessage, const MessageInfo &aMessageInfo)>
    class SocketIn : public Socket
    {
    public:
        /**
         * Initializes the socket.
         *
         * @param[in]  aInstance   The OpenThread instance.
         * @param[in]  aOnwer      The owner of the socket, providing the `HandleUdpReceivePtr` callback.
         */
        explicit SocketIn(Instance &aInstance, Owner &aOwner)
            : Socket(aInstance, HandleUdpReceive, &aOwner)
        {
        }

    private:
        static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
        {
            (reinterpret_cast<Owner *>(aContext)->*HandleUdpReceivePtr)(AsCoreType(aMessage), AsCoreType(aMessageInfo));
        }
    };

    /**
     * Implements a UDP receiver.
     */
    class Receiver : public otUdpReceiver, public LinkedListEntry<Receiver>
    {
        friend class Udp;

    public:
        /**
         * Initializes the UDP receiver.
         *
         * @param[in]   aHandler     A pointer to the function to handle UDP message.
         * @param[in]   aContext     A pointer to arbitrary context information.
         */
        Receiver(otUdpHandler aHandler, void *aContext)
        {
            mNext    = nullptr;
            mHandler = aHandler;
            mContext = aContext;
        }

    private:
        bool HandleMessage(Message &aMessage, const MessageInfo &aMessageInfo)
        {
            return mHandler(mContext, &aMessage, &aMessageInfo);
        }
    };

    /**
     * Initializes the object.
     *
     * @param[in]  aInstance  A reference to OpenThread instance.
     */
    explicit Udp(Instance &aInstance);

    /**
     * Adds a UDP receiver.
     *
     * @param[in]  aReceiver  A reference to the UDP receiver.
     *
     * @retval kErrorNone    Successfully added the UDP receiver.
     * @retval kErrorAlready The UDP receiver was already added.
     */
    Error AddReceiver(Receiver &aReceiver);

    /**
     * Removes a UDP receiver.
     *
     * @param[in]  aReceiver  A reference to the UDP receiver.
     *
     * @retval kErrorNone       Successfully removed the UDP receiver.
     * @retval kErrorNotFound   The UDP receiver was not added.
     */
    Error RemoveReceiver(Receiver &aReceiver);

    /**
     * Opens a UDP socket.
     *
     * @param[in]  aSocket   A reference to the socket.
     * @param[in]  aNetifId  A network interface identifier.
     * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
     * @param[in]  aContext  A pointer to arbitrary context information.
     *
     * @retval kErrorNone     Successfully opened the socket.
     * @retval kErrorFailed   Failed to open the socket.
     */
    Error Open(SocketHandle &aSocket, NetifIdentifier aNetifId, ReceiveHandler aHandler, void *aContext);

    /**
     * Returns if a UDP socket is open.
     *
     * @param[in]  aSocket   A reference to the socket.
     *
     * @returns If the UDP socket is open.
     */
    bool IsOpen(const SocketHandle &aSocket) const { return mSockets.Contains(aSocket); }

    /**
     * Binds a UDP socket.
     *
     * @param[in]  aSocket          A reference to the socket.
     * @param[in]  aSockAddr        A reference to the socket address.
     *
     * @retval kErrorNone            Successfully bound the socket.
     * @retval kErrorInvalidArgs     Unable to bind to Thread network interface with the given address.
     * @retval kErrorFailed          Failed to bind UDP Socket.
     */
    Error Bind(SocketHandle &aSocket, const SockAddr &aSockAddr);

    /**
     * Connects a UDP socket.
     *
     * @param[in]  aSocket    A reference to the socket.
     * @param[in]  aSockAddr  A reference to the socket address.
     *
     * @retval kErrorNone    Successfully connected the socket.
     * @retval kErrorFailed  Failed to connect UDP Socket.
     */
    Error Connect(SocketHandle &aSocket, const SockAddr &aSockAddr);

    /**
     * Closes the UDP socket.
     *
     * @param[in]  aSocket    A reference to the socket.
     *
     * @retval kErrorNone    Successfully closed the UDP socket.
     * @retval kErrorFailed  Failed to close UDP Socket.
     */
    Error Close(SocketHandle &aSocket);

    /**
     * Sends a UDP message using a socket.
     *
     * @param[in]  aSocket       A reference to the socket.
     * @param[in]  aMessage      The message to send.
     * @param[in]  aMessageInfo  The message info associated with @p aMessage.
     *
     * @retval kErrorNone         Successfully sent the UDP message.
     * @retval kErrorInvalidArgs  If no peer is specified in @p aMessageInfo or by Connect().
     * @retval kErrorNoBufs       Insufficient available buffer to add the UDP and IPv6 headers.
     */
    Error SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo);

    /**
     * Returns a new ephemeral port.
     *
     * @returns A new ephemeral port.
     */
    uint16_t GetEphemeralPort(void);

    /**
     * Sends an IPv6 datagram.
     *
     * @param[in]  aMessage      A reference to the message.
     * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
     *
     * @retval kErrorNone    Successfully enqueued the message into an output interface.
     * @retval kErrorNoBufs  Insufficient available buffer to add the IPv6 headers.
     */
    Error SendDatagram(Message &aMessage, MessageInfo &aMessageInfo);

    /**
     * Handles a received UDP message.
     *
     * @param[in]  aMessage      A reference to the UDP message to process.
     * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
     *
     * @retval kErrorNone  Successfully processed the UDP message.
     * @retval kErrorDrop  Could not fully process the UDP message.
     */
    Error HandleMessage(Message &aMessage, MessageInfo &aMessageInfo);

    /**
     * Handles a received UDP message with offset set to the payload.
     *
     * @param[in]  aMessage      A reference to the UDP message to process.
     * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
     */
    void HandlePayload(Message &aMessage, MessageInfo &aMessageInfo);

    /**
     * Returns the UDP Sockets linked list.
     *
     * @returns The UDP Sockets linked list.
     */
    LinkedList<SocketHandle> &GetUdpSockets(void) { return mSockets; }

#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
    /**
     * Sets the forward sender.
     *
     * @param[in]   aForwarder  A function pointer to forward UDP packets.
     * @param[in]   aContext    A pointer to arbitrary context information.
     */
    void SetUdpForwarder(otUdpForwarder aForwarder, void *aContext) { mUdpForwarder.Set(aForwarder, aContext); }
#endif

    /**
     * Returns whether a udp port is being used by OpenThread or any of it's optional
     * features, e.g. CoAP API.
     *
     * @param[in]   aPort       The udp port
     *
     * @retval True when port is used by the OpenThread.
     * @retval False when the port is not used by OpenThread.
     */
    bool IsPortInUse(uint16_t aPort) const;

private:
    static constexpr uint16_t kDynamicPortMin = 49152; // Service Name and Transport Protocol Port Number Registry
    static constexpr uint16_t kDynamicPortMax = 65535; // Service Name and Transport Protocol Port Number Registry

    // Reserved range for use by SRP server
    static constexpr uint16_t kSrpServerPortMin = OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT_MIN;
    static constexpr uint16_t kSrpServerPortMax = OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT_MAX;

#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
    struct Plat
    {
        static Error Open(SocketHandle &aSocket);
        static Error Close(SocketHandle &aSocket);
        static Error Bind(SocketHandle &aSocket);
        static Error BindToNetif(SocketHandle &aSocket);
        static Error Connect(SocketHandle &aSocket);
        static Error Send(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo);
        static Error JoinMulticastGroup(SocketHandle &aSocket, NetifIdentifier aNetifId, const Address &aAddress);
        static Error LeaveMulticastGroup(SocketHandle &aSocket, NetifIdentifier aNetifId, const Address &aAddress);
    };
#endif

    static bool IsPortReserved(uint16_t aPort);

    void AddSocket(SocketHandle &aSocket);
    void RemoveSocket(SocketHandle &aSocket);

    uint16_t                 mEphemeralPort;
    LinkedList<Receiver>     mReceivers;
    LinkedList<SocketHandle> mSockets;
#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
    Callback<otUdpForwarder> mUdpForwarder;
#endif
};

/**
 * @}
 */

} // namespace Ip6

DefineCoreType(otUdpSocket, Ip6::Udp::SocketHandle);
DefineCoreType(otUdpReceiver, Ip6::Udp::Receiver);
DefineMapEnum(otNetifIdentifier, Ip6::NetifIdentifier);

} // namespace ot

#endif // OT_CORE_NET_UDP6_HPP_
