/*
 *  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 for TCP/IPv6 sockets.
 */

#ifndef OT_CORE_NET_TCP6_HPP_
#define OT_CORE_NET_TCP6_HPP_

#include "openthread-core-config.h"

#include <openthread/tcp.h>

#include "common/as_core_type.hpp"
#include "common/clearable.hpp"
#include "common/linked_list.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/timer.hpp"
#include "net/ip6_headers.hpp"
#include "net/socket.hpp"

/*
 * These structures and functions are forward-declared here to avoid
 * #includ'ing third_party/tcplp/tcplp.h in this header file.
 */
extern "C" {
struct tcpcb;
struct tcpcb_listen;
struct tcplp_signals;

/*
 * The next two declarations intentionally change argument names from the
 * original declarations in TCPlp, in order to comply with OpenThread's format.
 */

// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
void tcplp_sys_set_timer(struct tcpcb *aTcb, uint8_t aTimerFlag, uint32_t aDelay);

// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
void tcplp_sys_stop_timer(struct tcpcb *aTcb, uint8_t aTimerFlag);
}

namespace ot {
namespace Ip6 {

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

/**
 * Implements TCP message handling.
 */
class Tcp : public InstanceLocator, private NonCopyable
{
public:
    typedef TcpHeader Header; ///< TCP Header.

    /**
     * Represents an endpoint of a TCP/IPv6 connection.
     */
    class Endpoint : public otTcpEndpoint, public LinkedListEntry<Endpoint>, public GetProvider<Endpoint>
    {
        friend class Tcp;
        friend class LinkedList<Endpoint>;

    public:
        /**
         * Initializes a TCP endpoint.
         *
         * Calling this function causes OpenThread to keep track of this Endpoint
         * and store and retrieve TCP data inside of it. The application
         * should refrain from directly accessing or modifying the fields in
         * this Endpoint. If the application needs to reclaim the memory backing
         * this Endpoint, it should call otTcpEndpointDeinitialize().
         *
         * @sa otTcpEndpointInitialize in include/openthread/tcp.h.
         *
         * @param[in]  aInstance  A pointer to an OpenThread instance.
         * @param[in]  aArgs      A pointer to a structure of arguments.
         *
         * @retval kErrorNone    Successfully opened the TCP endpoint.
         * @retval kErrorFailed  Failed to open the TCP endpoint.
         */
        Error Initialize(Instance &aInstance, const otTcpEndpointInitializeArgs &aArgs);

        /**
         * Obtains the Instance that was associated with this Endpoint upon
         * initialization.
         *
         * @sa otTcpEndpointGetInstance
         *
         * @returns  The Instance pointer associated with this Endpoint.
         */
        Instance &GetInstance(void) const;

        /**
         * Obtains the context pointer that was associated this Endpoint upon
         * initialization.
         *
         * @sa otTcpEndpointGetContext
         *
         * @returns  The context pointer associated with this Endpoint.
         */
        void *GetContext(void) { return mContext; }

        /**
         * Obtains a pointer to a TCP endpoint's local host and port.
         *
         * The contents of the host and port may be stale if this socket is not in a
         * connected state and has not been bound after it was last disconnected.
         *
         * @sa otTcpGetLocalAddress
         *
         * @returns  The local host and port of this Endpoint.
         */
        const SockAddr &GetLocalAddress(void) const;

        /**
         * Obtains a pointer to a TCP endpoint's peer's host and port.
         *
         * The contents of the host and port may be stale if this socket is not in a
         * connected state.
         *
         * @sa otTcpGetPeerAddress
         *
         * @returns  The host and port of the connection peer of this Endpoint.
         */
        const SockAddr &GetPeerAddress(void) const;

        /**
         * Binds the TCP endpoint to an IP address and port.
         *
         * @sa otTcpBind
         *
         * @param[in]  aSockName   The address and port to which to bind this TCP endpoint.
         *
         * @retval kErrorNone    Successfully bound the TCP endpoint.
         * @retval kErrorFailed  Failed to bind the TCP endpoint.
         */
        Error Bind(const SockAddr &aSockName);

        /**
         * Records the remote host and port for this connection.
         *
         * By default TCP Fast Open is used. This means that this function merely
         * records the remote host and port, and that the TCP connection establishment
         * handshake only happens on the first call to otTcpSendByReference(). TCP Fast
         * Open can be explicitly disabled using @p aFlags, in which case the TCP
         * connection establishment handshake is initiated immediately.
         *
         * @sa otTcpConnect
         *
         * @param[in]  aSockName  The IP address and port of the host to which to connect.
         * @param[in]  aFlags     Flags specifying options for this operation (see enumeration above).
         *
         * @retval kErrorNone    Successfully completed the operation.
         * @retval kErrorFailed  Failed to complete the operation.
         */
        Error Connect(const SockAddr &aSockName, uint32_t aFlags);

        /**
         * Adds data referenced by the linked buffer pointed to by @p aBuffer to the
         * send buffer.
         *
         * Upon a successful call to this function, the linked buffer and data it
         * references are owned by the TCP stack; they should not be modified by the
         * application until a "send done" callback returns ownership of those objects
         * to the application. It is acceptable to call this function to add another
         * linked buffer to the send queue, even if the "send done" callback for a
         * previous invocation of this function has not yet fired.
         *
         * Note that @p aBuffer should not be chained; its mNext field should be
         * NULL. If additional data will be added right after this call, then the
         * OT_TCP_SEND_MORE_TO_COME flag should be used as a hint to the TCP
         * implementation.
         *
         * @sa otTcpSendByReference
         *
         * @param[in]  aBuffer    A pointer to the linked buffer chain referencing data to add to the send buffer.
         * @param[in]  aFlags     Flags specifying options for this operation (see enumeration above).
         *
         * @retval kErrorNone    Successfully added data to the send buffer.
         * @retval kErrorFailed  Failed to add data to the send buffer.
         */
        Error SendByReference(otLinkedBuffer &aBuffer, uint32_t aFlags);

        /**
         * Adds data to the send buffer by extending the length of the final
         * otLinkedBuffer in the send buffer by the specified amount.
         *
         * If the send buffer is empty, then the operation fails.
         *
         * @sa otTcpSendByExtension
         *
         * @param[in]  aNumBytes  The number of bytes by which to extend the length of the final linked buffer.
         * @param[in]  aFlags     Flags specifying options for this operation (see enumeration above).
         *
         * @retval kErrorNone    Successfully added data to the send buffer.
         * @retval kErrorFailed  Failed to add data to the send buffer.
         */
        Error SendByExtension(size_t aNumBytes, uint32_t aFlags);

        /**
         * Provides the application with a linked buffer chain referencing data
         * currently in the TCP receive buffer.
         *
         * The linked buffer chain is valid until the "receive ready" callback is next
         * invoked, or until the next call to otTcpReceiveContiguify() or
         * otTcpCommitReceive().
         *
         * @sa otTcpReceiveByReference
         *
         * @param[out]  aBuffer    A pointer to the linked buffer chain referencing data currently in the receive
         * buffer.
         *
         * @retval kErrorNone    Successfully completed the operation.
         * @retval kErrorFailed  Failed to complete the operation.
         */
        Error ReceiveByReference(const otLinkedBuffer *&aBuffer);

        /**
         * Reorganizes the receive buffer to be entirely contiguous in memory.
         *
         * This is optional; an application can simply traverse the linked buffer
         * chain obtained by calling @p otTcpReceiveByReference. Some
         * applications may wish to call this function to make the receive buffer
         * contiguous to simplify their data processing, but this comes at the expense
         * of CPU time to reorganize the data in the receive buffer.
         *
         * @sa otTcpReceiveContiguify
         *
         * @retval kErrorNone    Successfully completed the operation.
         * @retval kErrorFailed  Failed to complete the operation.
         */
        Error ReceiveContiguify(void);

        /**
         * Informs the TCP stack that the application has finished processing
         * @p aNumBytes bytes of data at the start of the receive buffer and that the
         * TCP stack need not continue maintaining those bytes in the receive buffer.
         *
         * @sa otTcpCommitReceive
         *
         * @param[in]  aNumBytes  The number of bytes consumed.
         * @param[in]  aFlags     Flags specifying options for this operation (none yet).
         *
         * @retval kErrorNone    Successfully completed the receive operation.
         * @retval kErrorFailed  Failed to complete the receive operation.
         */
        Error CommitReceive(size_t aNumBytes, uint32_t aFlags);

        /**
         * Informs the connection peer that this TCP endpoint will not send more data.
         *
         * This should be used when the application has no more data to send to the
         * connection peer. For this connection, future reads on the connection peer
         * will result in the "end of stream" condition, and future writes on this
         * connection endpoint will fail.
         *
         * The "end of stream" condition only applies after any data previously
         * provided to the TCP stack to send out has been received by the connection
         * peer.
         *
         * @sa otTcpSendEndOfStream
         *
         * @retval kErrorNone    Successfully queued the "end of stream" condition for transmission.
         * @retval kErrorFailed  Failed to queue the "end of stream" condition for transmission.
         */
        Error SendEndOfStream(void);

        /**
         * Forcibly ends the TCP connection associated with this TCP endpoint.
         *
         * This immediately makes the TCP endpoint free for use for another connection
         * and empties the send and receive buffers, transferring ownership of any data
         * provided by the application in otTcpSendByReference() calls back to
         * the application. The TCP endpoint's callbacks and memory for the receive
         * buffer remain associated with the TCP endpoint.
         *
         * @sa otTcpAbort
         *
         * @retval kErrorNone    Successfully aborted the TCP endpoint's connection.
         * @retval kErrorFailed  Failed to abort the TCP endpoint's connection.
         */
        Error Abort(void);

        /**
         * Deinitializes this TCP endpoint.
         *
         * This means that OpenThread no longer keeps track of this TCP endpoint and
         * deallocates all resources it has internally allocated for this TCP endpoint.
         * The application can reuse the memory backing the TCP endpoint as it sees fit.
         *
         * If it corresponds to a live TCP connection, the connection is terminated
         * unceremoniously (as in otTcpAbort()). All resources the application has
         * provided for this TCP endpoint (linked buffers for the send buffer, memory
         * for the receive buffer, this Endpoint structure itself, etc.) are
         * immediately returned to the application.
         *
         * @sa otTcpEndpointDeinitialize
         *
         * @retval kErrorNone    Successfully deinitialized the TCP endpoint.
         * @retval kErrorFailed  Failed to deinitialize the TCP endpoint.
         */
        Error Deinitialize(void);

        /**
         * Converts a reference to a struct tcpcb to a reference to its
         * enclosing Endpoint.
         */
        static Endpoint &FromTcb(struct tcpcb &aTcb) { return *reinterpret_cast<Endpoint *>(&aTcb); }

        /**
         * Obtains a reference to this Endpoint's struct tcpcb.
         */
        struct tcpcb &GetTcb(void) { return *reinterpret_cast<struct tcpcb *>(&mTcb); }

        /**
         * Obtains a const reference to this Endpoint's struct tcpcb.
         */
        const struct tcpcb &GetTcb(void) const { return *reinterpret_cast<const struct tcpcb *>(&mTcb); }

        /**
         * Checks if this Endpoint is in the closed state.
         */
        bool IsClosed(void) const;

    private:
        friend void ::tcplp_sys_set_timer(struct tcpcb *aTcb, uint8_t aTimerFlag, uint32_t aDelay);
        friend void ::tcplp_sys_stop_timer(struct tcpcb *aTcb, uint8_t aTimerFlag);

        static constexpr uint8_t kTimerDelack       = 0;
        static constexpr uint8_t kTimerRexmtPersist = 1;
        static constexpr uint8_t kTimerKeep         = 2;
        static constexpr uint8_t kTimer2Msl         = 3;
        static constexpr uint8_t kNumTimers         = 4;

        static uint8_t TimerFlagToIndex(uint8_t aTimerFlag);

        bool IsTimerActive(uint8_t aTimerIndex);
        void SetTimer(uint8_t aTimerFlag, uint32_t aDelay);
        void CancelTimer(uint8_t aTimerFlag);
        bool FirePendingTimers(TimeMilli aNow, bool &aHasFutureTimer, TimeMilli &aEarliestFutureExpiry);

        void PostCallbacksAfterSend(size_t aSent, size_t aBacklogBefore);
        bool FirePendingCallbacks(void);

        size_t GetSendBufferBytes(void) const;
        size_t GetInFlightBytes(void) const;
        size_t GetBacklogBytes(void) const;

        Address       &GetLocalIp6Address(void);
        const Address &GetLocalIp6Address(void) const;
        Address       &GetForeignIp6Address(void);
        const Address &GetForeignIp6Address(void) const;
        bool           Matches(const MessageInfo &aMessageInfo) const;
    };

    /**
     * Represents a TCP/IPv6 listener.
     */
    class Listener : public otTcpListener, public LinkedListEntry<Listener>, public GetProvider<Listener>
    {
        friend class LinkedList<Listener>;

    public:
        /**
         * Initializes a TCP listener.
         *
         * Calling this function causes OpenThread to keep track of the TCP listener
         * and store and retrieve TCP data inside this Listener. The application should
         * refrain from directly accessing or modifying the fields in this Listener. If
         * the application needs to reclaim the memory backing this Listener, it should
         * call otTcpListenerDeinitialize().
         *
         * @sa otTcpListenerInitialize
         *
         * @param[in]  aInstance  A pointer to an OpenThread instance.
         * @param[in]  aArgs      A pointer to a structure of arguments.
         *
         * @retval kErrorNone    Successfully opened the TCP listener.
         * @retval kErrorFailed  Failed to open the TCP listener.
         */
        Error Initialize(Instance &aInstance, const otTcpListenerInitializeArgs &aArgs);

        /**
         * Obtains the otInstance that was associated with this Listener upon
         * initialization.
         *
         * @sa otTcpListenerGetInstance
         *
         * @returns  The otInstance pointer associated with this Listener.
         */
        Instance &GetInstance(void) const;

        /**
         * Obtains the context pointer that was associated with this Listener upon
         * initialization.
         *
         * @sa otTcpListenerGetContext
         *
         * @returns  The context pointer associated with this Listener.
         */
        void *GetContext(void) { return mContext; }

        /**
         * Causes incoming TCP connections that match the specified IP address and port
         * to trigger this TCP listener's callbacks.
         *
         * @sa otTcpListen
         *
         * @param[in]  aSockName  The address and port on which to listen for incoming connections.
         *
         * @retval kErrorNone    Successfully initiated listening on the TCP listener.
         * @retval kErrorFailed  Failed to initiate listening on the TCP listener.
         */
        Error Listen(const SockAddr &aSockName);

        /**
         * Causes this TCP listener to stop listening for incoming connections.
         *
         * @sa otTcpStopListening
         *
         * @retval kErrorNone    Successfully stopped listening on the TCP listener.
         * @retval kErrorFailed  Failed to stop listening on the TCP listener.
         */
        Error StopListening(void);

        /**
         * Deinitializes this TCP listener.
         *
         * This means that OpenThread no longer keeps track of this TCP listener and
         * deallocates all resources it has internally allocated for this TCP endpoint.
         * The application can reuse the memory backing the TCP listener as it sees
         * fit.
         *
         * If the TCP listener is currently listening, it stops listening.
         *
         * @sa otTcpListenerDeinitialize
         *
         * @retval kErrorNone    Successfully deinitialized the TCP listener.
         * @retval kErrorFailed  Failed to deinitialize the TCP listener.
         */
        Error Deinitialize(void);

        /**
         * Converts a reference to a struct tcpcb_listen to a reference to its
         * enclosing Listener.
         */
        static Listener &FromTcbListen(struct tcpcb_listen &aTcbListen)
        {
            return *reinterpret_cast<Listener *>(&aTcbListen);
        }

        /**
         * Obtains a reference to this Listener's struct tcpcb_listen.
         */
        struct tcpcb_listen &GetTcbListen(void) { return *reinterpret_cast<struct tcpcb_listen *>(&mTcbListen); }

        /**
         * Obtains a const reference to this Listener's struct tcpcb_listen.
         */
        const struct tcpcb_listen &GetTcbListen(void) const
        {
            return *reinterpret_cast<const struct tcpcb_listen *>(&mTcbListen);
        }

        /**
         * Checks if this Listener is in the closed state.
         */
        bool IsClosed(void) const;

    private:
        Address       &GetLocalIp6Address(void);
        const Address &GetLocalIp6Address(void) const;
        bool           Matches(const MessageInfo &aMessageInfo) const;
    };

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

    /**
     * Processes a received TCP segment.
     *
     * @param[in]  aIp6Header    A reference to a structure containing the segment's IPv6 header.
     * @param[in]  aMessage      A reference to the message containing the TCP segment.
     * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
     *
     * @retval kErrorNone  Successfully processed the TCP segment.
     * @retval kErrorDrop  Dropped the TCP segment due to an invalid checksum.
     */
    Error HandleMessage(ot::Ip6::Header &aIp6Header, Message &aMessage, MessageInfo &aMessageInfo);

    /**
     * Automatically selects a local address and/or port for communication with the specified peer.
     *
     * @param[in] aPeer         The peer's address and port.
     * @param[in,out] aToBind   The SockAddr into which to store the selected address and/or port.
     * @param[in] aBindAddress  If true, the local address is selected; if not, the current address
     *                          in @p aToBind is treated as a given.
     * @param[in] aBindPort     If true, the local port is selected; if not, the current port in
     *                          @p aToBind is treated as a given.
     *
     * @returns  True if successful, false otherwise.
     */
    bool AutoBind(const SockAddr &aPeer, SockAddr &aToBind, bool aBindAddress, bool aBindPort);

    /**
     * Checks if an Endpoint is in the list of initialized endpoints.
     */
    bool IsInitialized(const Endpoint &aEndpoint) const { return mEndpoints.Contains(aEndpoint); }

    /**
     * Checks if a Listener is in the list of initialized Listeners.
     */
    bool IsInitialized(const Listener &aListener) const { return mListeners.Contains(aListener); }

private:
    static constexpr uint16_t kDynamicPortMin = 49152;
    static constexpr uint16_t kDynamicPortMax = 65535;

    static constexpr uint8_t kEstablishedCallbackFlag      = (1 << 0);
    static constexpr uint8_t kSendDoneCallbackFlag         = (1 << 1);
    static constexpr uint8_t kForwardProgressCallbackFlag  = (1 << 2);
    static constexpr uint8_t kReceiveAvailableCallbackFlag = (1 << 3);
    static constexpr uint8_t kDisconnectedCallbackFlag     = (1 << 4);

    void ProcessSignals(Endpoint             &aEndpoint,
                        otLinkedBuffer       *aPriorHead,
                        size_t                aPriorBacklog,
                        struct tcplp_signals &aSignals) const;

    static Error BsdErrorToOtError(int aBsdError);
    bool         CanBind(const SockAddr &aSockName);

    void HandleTimer(void);

    void ProcessCallbacks(void);

    using TcpTasklet = TaskletIn<Tcp, &Tcp::ProcessCallbacks>;
    using TcpTimer   = TimerMilliIn<Tcp, &Tcp::HandleTimer>;

    TcpTimer   mTimer;
    TcpTasklet mTasklet;

    LinkedList<Endpoint> mEndpoints;
    LinkedList<Listener> mListeners;
    uint16_t             mEphemeralPort;
};

} // namespace Ip6

DefineCoreType(otTcpEndpoint, Ip6::Tcp::Endpoint);
DefineCoreType(otTcpListener, Ip6::Tcp::Listener);

} // namespace ot

#endif // OT_CORE_NET_TCP6_HPP_
