/*
 * Copyright (c) 2007 Georgia Tech Research Corporation
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Raj Bhattacharjea <raj.b@gatech.edu>
 */

#ifndef TCP_L4_PROTOCOL_H
#define TCP_L4_PROTOCOL_H

#include "ip-l4-protocol.h"

#include "ns3/ipv4-address.h"
#include "ns3/ipv6-address.h"
#include "ns3/sequence-number.h"

#include <stdint.h>
#include <unordered_map>

namespace ns3
{

class Node;
class Socket;
class TcpHeader;
class Ipv4EndPointDemux;
class Ipv6EndPointDemux;
class Ipv4Interface;
class TcpSocketBase;
class Ipv4EndPoint;
class Ipv6EndPoint;
class NetDevice;

/**
 * @ingroup internet
 * @defgroup tcp TCP
 *
 * This is an implementation of various Transmission Control Protocol flavors.
 *
 * Each TCP flavors is studied to match a specific environment, and they
 * differ mainly in the congestion control algorithms used.
 *
 * See \RFC{793} and others.
 */

/**
 * @ingroup tcp
 * @brief TCP socket creation and multiplexing/demultiplexing
 *
 * A single instance of this class is held by one instance of class Node.
 *
 * The creation of TcpSocket are handled in the method CreateSocket, which is
 * called by TcpSocketFactory. Upon creation, this class is responsible to
 * the socket initialization and handle multiplexing/demultiplexing of data
 * between node's TCP sockets. Demultiplexing is done by receiving
 * packets from IP, and forwards them up to the right socket. Multiplexing
 * is done through the SendPacket function, which sends the packet down the stack.
 *
 * Moreover, this class allocates "endpoint" objects (ns3::Ipv4EndPoint) for TCP,
 * and SHOULD checksum packets its receives from the socket layer going down
 * the stack, but currently checksumming is disabled.
 *
 * @see CreateSocket
 * @see NotifyNewAggregate
 * @see SendPacket
 */

class TcpL4Protocol : public IpL4Protocol
{
  public:
    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /// Protocol number (see http://www.iana.org/assignments/protocol-numbers)
    static constexpr uint8_t PROT_NUMBER = 6;

    TcpL4Protocol();
    ~TcpL4Protocol() override;

    // Delete copy constructor and assignment operator to avoid misuse
    TcpL4Protocol(const TcpL4Protocol&) = delete;
    TcpL4Protocol& operator=(const TcpL4Protocol&) = delete;

    /**
     * Set node associated with this stack
     * @param node the node
     */
    void SetNode(Ptr<Node> node);

    // NOTE: API from here should not be removed, only added. Be backward-compatible!

    /**
     * @brief Create a TCP socket using the TypeId set by SocketType attribute
     *
     * @return A smart Socket pointer to a TcpSocket allocated by this instance
     * of the TCP protocol
     */
    Ptr<Socket> CreateSocket();

    /**
     * @brief Create a TCP socket using the specified congestion control algorithm TypeId
     *
     * @return A smart Socket pointer to a TcpSocket allocated by this instance
     * of the TCP protocol
     *
     * @warning using a congestionTypeId other than TCP is a bad idea.
     *
     * @param congestionTypeId the congestion control algorithm TypeId
     * @param recoveryTypeId the recovery algorithm TypeId
     */
    Ptr<Socket> CreateSocket(TypeId congestionTypeId, TypeId recoveryTypeId);

    /**
     * @brief Create a TCP socket using the specified congestion control algorithm
     * @return A smart Socket pointer to a TcpSocket allocated by this instance
     * of the TCP protocol
     *
     * @param congestionTypeId the congestion control algorithm TypeId
     *
     */
    Ptr<Socket> CreateSocket(TypeId congestionTypeId);

    /**
     * @brief Allocate an IPv4 Endpoint
     * @return the Endpoint
     */
    Ipv4EndPoint* Allocate();
    /**
     * @brief Allocate an IPv4 Endpoint
     * @param address address to use
     * @return the Endpoint
     */
    Ipv4EndPoint* Allocate(Ipv4Address address);
    /**
     * @brief Allocate an IPv4 Endpoint
     * @param boundNetDevice Bound NetDevice (if any)
     * @param port port to use
     * @return the Endpoint
     */
    Ipv4EndPoint* Allocate(Ptr<NetDevice> boundNetDevice, uint16_t port);
    /**
     * @brief Allocate an IPv4 Endpoint
     * @param boundNetDevice Bound NetDevice (if any)
     * @param address address to use
     * @param port port to use
     * @return the Endpoint
     */
    Ipv4EndPoint* Allocate(Ptr<NetDevice> boundNetDevice, Ipv4Address address, uint16_t port);
    /**
     * @brief Allocate an IPv4 Endpoint
     * @param boundNetDevice Bound NetDevice (if any)
     * @param localAddress local address to use
     * @param localPort local port to use
     * @param peerAddress remote address to use
     * @param peerPort remote port to use
     * @return the Endpoint
     */
    Ipv4EndPoint* Allocate(Ptr<NetDevice> boundNetDevice,
                           Ipv4Address localAddress,
                           uint16_t localPort,
                           Ipv4Address peerAddress,
                           uint16_t peerPort);
    /**
     * @brief Allocate an IPv6 Endpoint
     * @return the Endpoint
     */
    Ipv6EndPoint* Allocate6();
    /**
     * @brief Allocate an IPv6 Endpoint
     * @param address address to use
     * @return the Endpoint
     */
    Ipv6EndPoint* Allocate6(Ipv6Address address);
    /**
     * @brief Allocate an IPv6 Endpoint
     * @param boundNetDevice Bound NetDevice (if any)
     * @param port port to use
     * @return the Endpoint
     */
    Ipv6EndPoint* Allocate6(Ptr<NetDevice> boundNetDevice, uint16_t port);
    /**
     * @brief Allocate an IPv6 Endpoint
     * @param boundNetDevice Bound NetDevice (if any)
     * @param address address to use
     * @param port port to use
     * @return the Endpoint
     */
    Ipv6EndPoint* Allocate6(Ptr<NetDevice> boundNetDevice, Ipv6Address address, uint16_t port);
    /**
     * @brief Allocate an IPv6 Endpoint
     * @param boundNetDevice Bound NetDevice (if any)
     * @param localAddress local address to use
     * @param localPort local port to use
     * @param peerAddress remote address to use
     * @param peerPort remote port to use
     * @return the Endpoint
     */
    Ipv6EndPoint* Allocate6(Ptr<NetDevice> boundNetDevice,
                            Ipv6Address localAddress,
                            uint16_t localPort,
                            Ipv6Address peerAddress,
                            uint16_t peerPort);

    /**
     * @brief Send a packet via TCP (IP-agnostic)
     *
     * @param pkt The packet to send
     * @param outgoing The packet header
     * @param saddr The source Ipv4Address
     * @param daddr The destination Ipv4Address
     * @param oif The output interface bound. Defaults to null (unspecified).
     */
    void SendPacket(Ptr<Packet> pkt,
                    const TcpHeader& outgoing,
                    const Address& saddr,
                    const Address& daddr,
                    Ptr<NetDevice> oif = nullptr) const;

    /**
     * @brief Make a socket fully operational
     *
     * Called after a socket has been bound, it is inserted in an internal vector.
     *
     * @param socket Socket to be added
     */
    void AddSocket(Ptr<TcpSocketBase> socket);

    /**
     * @brief Remove a socket from the internal list
     *
     * @param socket socket to Remove
     * @return true if the socket has been removed
     */
    bool RemoveSocket(Ptr<TcpSocketBase> socket);

    /**
     * @brief Remove an IPv4 Endpoint.
     * @param endPoint the end point to remove
     */
    void DeAllocate(Ipv4EndPoint* endPoint);
    /**
     * @brief Remove an IPv6 Endpoint.
     * @param endPoint the end point to remove
     */
    void DeAllocate(Ipv6EndPoint* endPoint);

    // From IpL4Protocol
    IpL4Protocol::RxStatus Receive(Ptr<Packet> p,
                                   const Ipv4Header& incomingIpHeader,
                                   Ptr<Ipv4Interface> incomingInterface) override;
    IpL4Protocol::RxStatus Receive(Ptr<Packet> p,
                                   const Ipv6Header& incomingIpHeader,
                                   Ptr<Ipv6Interface> incomingInterface) override;

    void ReceiveIcmp(Ipv4Address icmpSource,
                     uint8_t icmpTtl,
                     uint8_t icmpType,
                     uint8_t icmpCode,
                     uint32_t icmpInfo,
                     Ipv4Address payloadSource,
                     Ipv4Address payloadDestination,
                     const uint8_t payload[8]) override;
    void ReceiveIcmp(Ipv6Address icmpSource,
                     uint8_t icmpTtl,
                     uint8_t icmpType,
                     uint8_t icmpCode,
                     uint32_t icmpInfo,
                     Ipv6Address payloadSource,
                     Ipv6Address payloadDestination,
                     const uint8_t payload[8]) override;

    void SetDownTarget(IpL4Protocol::DownTargetCallback cb) override;
    void SetDownTarget6(IpL4Protocol::DownTargetCallback6 cb) override;
    int GetProtocolNumber() const override;
    IpL4Protocol::DownTargetCallback GetDownTarget() const override;
    IpL4Protocol::DownTargetCallback6 GetDownTarget6() const override;

  protected:
    void DoDispose() override;

    /**
     * @brief Setup socket factory and callbacks when aggregated to a node
     *
     * This function will notify other components connected to the node that a
     * new stack member is now connected. This will be used to notify Layer 3
     * protocol of layer 4 protocol stack to connect them together.
     * The aggregation is completed by setting the node in the TCP stack, link
     * it to the ipv4 or ipv6 stack and adding TCP socket factory to the node.
     */
    void NotifyNewAggregate() override;

    /**
     * @brief Get the tcp header of the incoming packet and checks its checksum if needed
     *
     * @param packet Received packet
     * @param incomingTcpHeader Overwritten with the tcp header of the packet
     * @param source Source address (an underlying Ipv4Address or Ipv6Address)
     * @param destination Destination address (an underlying Ipv4Address or Ipv6Address)
     *
     * @return RX_CSUM_FAILED if the checksum check fails, RX_OK otherwise
     */
    IpL4Protocol::RxStatus PacketReceived(Ptr<Packet> packet,
                                          TcpHeader& incomingTcpHeader,
                                          const Address& source,
                                          const Address& destination);

    /**
     * @brief Check if RST packet should be sent, and in case, send it
     *
     * The function is called when no endpoint is found for the received
     * packet. So TcpL4Protocol do not know to who the packet should be
     * given to. An RST packet is sent out as reply unless the received packet
     * has the RST flag set.
     *
     * @param incomingHeader TCP header of the incoming packet
     * @param incomingSAddr Source address of the incoming packet
     * @param incomingDAddr Destination address of the incoming packet
     *
     */
    void NoEndPointsFound(const TcpHeader& incomingHeader,
                          const Address& incomingSAddr,
                          const Address& incomingDAddr);

  private:
    Ptr<Node> m_node;                //!< the node this stack is associated with
    Ipv4EndPointDemux* m_endPoints;  //!< A list of IPv4 end points.
    Ipv6EndPointDemux* m_endPoints6; //!< A list of IPv6 end points.
    TypeId m_rttTypeId;              //!< The RTT Estimator TypeId
    TypeId m_congestionTypeId;       //!< The socket TypeId
    TypeId m_recoveryTypeId;         //!< The recovery TypeId
    std::unordered_map<uint64_t, Ptr<TcpSocketBase>>
        m_sockets;             //!< Unordered map of socket IDs and corresponding sockets
    uint64_t m_socketIndex{0}; //!< index of the next socket to be created
    IpL4Protocol::DownTargetCallback m_downTarget;   //!< Callback to send packets over IPv4
    IpL4Protocol::DownTargetCallback6 m_downTarget6; //!< Callback to send packets over IPv6

    /**
     * @brief Send a packet via TCP (IPv4)
     *
     * @param pkt The packet to send
     * @param outgoing The packet header
     * @param saddr The source Ipv4Address
     * @param daddr The destination Ipv4Address
     * @param oif The output interface bound. Defaults to null (unspecified).
     */
    void SendPacketV4(Ptr<Packet> pkt,
                      const TcpHeader& outgoing,
                      const Ipv4Address& saddr,
                      const Ipv4Address& daddr,
                      Ptr<NetDevice> oif = nullptr) const;

    /**
     * @brief Send a packet via TCP (IPv6)
     *
     * @param pkt The packet to send
     * @param outgoing The packet header
     * @param saddr The source Ipv4Address
     * @param daddr The destination Ipv4Address
     * @param oif The output interface bound. Defaults to null (unspecified).
     */
    void SendPacketV6(Ptr<Packet> pkt,
                      const TcpHeader& outgoing,
                      const Ipv6Address& saddr,
                      const Ipv6Address& daddr,
                      Ptr<NetDevice> oif = nullptr) const;
};

} // namespace ns3

#endif /* TCP_L4_PROTOCOL_H */
