/*
 * Copyright (c) 2018 Natale Patriciello <natale.patriciello@gmail.com>
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 */
#ifndef TCP_RATE_OPS_H
#define TCP_RATE_OPS_H

#include "tcp-tx-item.h"

#include "ns3/data-rate.h"
#include "ns3/object.h"
#include "ns3/traced-callback.h"
#include "ns3/traced-value.h"

namespace ns3
{

/**
 * @brief Interface for all operations that involve a Rate monitoring for TCP.
 *
 * The interface is meant to take information to generate rate information
 * valid for congestion avoidance algorithm such as BBR.
 *
 * Please take a look to the TcpRateLinux class for an example.
 */
class TcpRateOps : public Object
{
  public:
    struct TcpRateSample;
    struct TcpRateConnection;

    /**
     * Get the type ID.
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    /**
     * @brief Put the rate information inside the sent skb
     *
     * Snapshot the current delivery information in the skb, to generate
     * a rate sample later when the skb is (s)acked in SkbDelivered ().
     *
     * @param skb The SKB sent
     * @param isStartOfTransmission true if this is a start of transmission
     * (i.e., in_flight == 0)
     */
    virtual void SkbSent(TcpTxItem* skb, bool isStartOfTransmission) = 0;

    /**
     * @brief Update the Rate information after an item is received
     *
     * When an skb is sacked or acked, we fill in the rate sample with the (prior)
     * delivery information when the skb was last transmitted.
     *
     * If an ACK (s)acks multiple skbs (e.g., stretched-acks), this function is
     * called multiple times. We favor the information from the most recently
     * sent skb, i.e., the skb with the highest prior_delivered count.
     *
     * @param skb The SKB delivered ((s)ACKed)
     */
    virtual void SkbDelivered(TcpTxItem* skb) = 0;

    /**
     * @brief If a gap is detected between sends, it means we are app-limited.
     * TODO What the Linux kernel is setting in tp->app_limited?
     * https://elixir.bootlin.com/linux/latest/source/net/ipv4/tcp_rate.c#L177
     *
     * @param cWnd Congestion Window
     * @param in_flight In Flight size (in bytes)
     * @param segmentSize Segment size
     * @param tailSeq Tail Sequence
     * @param nextTx NextTx
     * @param lostOut Number of lost bytes
     * @param retransOut Number of retransmitted bytes
     */
    virtual void CalculateAppLimited(uint32_t cWnd,
                                     uint32_t in_flight,
                                     uint32_t segmentSize,
                                     const SequenceNumber32& tailSeq,
                                     const SequenceNumber32& nextTx,
                                     const uint32_t lostOut,
                                     const uint32_t retransOut) = 0;

    /**
     *
     * @brief Generate a TcpRateSample to feed a congestion avoidance algorithm.
     *
     * This function will be called after an ACK (or a SACK) is received. The
     * (S)ACK carries some implicit information, such as how many segments have been
     * lost or delivered. These values will be this function input.
     *
     * @param delivered number of delivered segments (e.g., receiving a cumulative
     * ACK means having more than 1 segment delivered) relative to the most recent
     * (S)ACK received
     * @param lost number of segments that we detected as lost after the reception
     * of the most recent (S)ACK
     * @param priorInFlight number of segments previously considered in flight
     * @param is_sack_reneg Is SACK reneged?
     * @param minRtt Minimum RTT so far
     * @return The TcpRateSample that will be used for CA
     */
    virtual const TcpRateSample& GenerateSample(uint32_t delivered,
                                                uint32_t lost,
                                                bool is_sack_reneg,
                                                uint32_t priorInFlight,
                                                const Time& minRtt) = 0;

    /**
     * @return The information about the rate connection
     *
     */
    virtual const TcpRateConnection& GetConnectionRate() = 0;
    /**
     * @brief Updates the app-limited state based on in-flight data.
     * @param in_flight In Flight size (in bytes)
     */
    virtual void SetAppLimited(uint32_t in_flight) = 0;

    /**
     * @brief Rate Sample structure
     *
     * A rate sample measures the number of (original/retransmitted) data
     * packets delivered "delivered" over an interval of time "interval_us".
     * The tcp_rate code fills in the rate sample, and congestion
     * control modules that define a cong_control function to run at the end
     * of ACK processing can optionally chose to consult this sample when
     * setting cwnd and pacing rate.
     * A sample is invalid if "delivered" or "interval_us" is negative.
     */
    struct TcpRateSample
    {
        DataRate m_deliveryRate{DataRate("0bps")}; //!< The delivery rate sample
        bool m_isAppLimited{false}; //!< Indicates whether the rate sample is application-limited
        Time m_interval;            //!< The length of the sampling interval
        int32_t m_delivered{
            0}; //!< The amount of data marked as delivered over the sampling interval
        uint32_t m_priorDelivered{0}; //!< The delivered count of the most recent packet delivered
        Time m_priorTime;             //!< The delivered time of the most recent packet delivered
        Time m_sendElapsed; //!< Send time interval calculated from the most recent packet delivered
        Time m_ackElapsed;  //!< ACK time interval calculated from the most recent packet delivered
        uint32_t m_bytesLoss{
            0}; //!< The amount of data marked as lost from the most recent ack received
        uint32_t m_priorInFlight{0}; //!< The value if bytes in flight prior to last received ack
        uint32_t m_ackedSacked{0}; //!< The amount of data acked and sacked in the last received ack

        /**
         * @brief Is the sample valid?
         * @return true if the sample is valid, false otherwise.
         */
        bool IsValid() const
        {
            return (!m_priorTime.IsZero() || !m_interval.IsZero());
        }
    };

    /**
     * @brief Information about the connection rate
     *
     * In this struct, the values are for the entire connection, and not just
     * for an interval of time
     */
    struct TcpRateConnection
    {
        uint64_t m_delivered{0}; //!< The total amount of data in bytes delivered so far
        Time m_deliveredTime;    //!< Simulator time when m_delivered was last updated
        Time m_firstSentTime;    //!< The send time of the packet that was most recently marked as
                                 //!< delivered
        uint32_t m_appLimited{
            0}; //!< The index of the last transmitted packet marked as application-limited
        uint32_t m_txItemDelivered{0}; //!< The value of delivered when the acked item was sent
        int32_t m_rateDelivered{
            0}; //!< The amount of data delivered considered to calculate delivery rate.
        Time m_rateInterval; //!< The value of interval considered to calculate delivery rate.
        bool m_rateAppLimited{false}; //!< Was sample was taken when data is app limited?
    };
};

/**
 * @brief Linux management and generation of Rate information for TCP
 *
 * This class is inspired by what Linux is performing in tcp_rate.c
 */
class TcpRateLinux : public TcpRateOps
{
  public:
    /**
     * Get the type ID.
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    ~TcpRateLinux() override
    {
    }

    void SkbSent(TcpTxItem* skb, bool isStartOfTransmission) override;
    void SkbDelivered(TcpTxItem* skb) override;
    void CalculateAppLimited(uint32_t cWnd,
                             uint32_t in_flight,
                             uint32_t segmentSize,
                             const SequenceNumber32& tailSeq,
                             const SequenceNumber32& nextTx,
                             const uint32_t lostOut,
                             const uint32_t retransOut) override;
    const TcpRateSample& GenerateSample(uint32_t delivered,
                                        uint32_t lost,
                                        bool is_sack_reneg,
                                        uint32_t priorInFlight,
                                        const Time& minRtt) override;

    const TcpRateConnection& GetConnectionRate() override
    {
        return m_rate;
    }

    /**
     * @brief Updates the app-limited state based on in-flight data.
     * @param in_flight In Flight size (in bytes)
     */
    void SetAppLimited(uint32_t in_flight) override;

    /**
     * TracedCallback signature for tcp rate update events.
     *
     * The callback will be fired each time the rate is updated.
     *
     * @param [in] rate The rate information.
     */
    typedef void (*TcpRateUpdated)(const TcpRateConnection& rate);

    /**
     * TracedCallback signature for tcp rate sample update events.
     *
     * The callback will be fired each time the rate sample is updated.
     *
     * @param [in] sample The rate sample that will be passed to congestion control
     * algorithms.
     */
    typedef void (*TcpRateSampleUpdated)(const TcpRateSample& sample);

  private:
    // Rate sample related variables
    TcpRateConnection m_rate;   //!< Rate information
    TcpRateSample m_rateSample; //!< Rate sample (continuously updated)

    TracedCallback<const TcpRateConnection&> m_rateTrace;   //!< Rate trace
    TracedCallback<const TcpRateSample&> m_rateSampleTrace; //!< Rate Sample trace
};

/**
 * @brief Output operator.
 * @param os The output stream.
 * @param sample the TcpRateLinux::TcpRateSample to print.
 * @returns The output stream.
 */
std::ostream& operator<<(std::ostream& os, const TcpRateOps::TcpRateSample& sample);

/**
 * @brief Output operator.
 * @param os The output stream.
 * @param rate the TcpRateLinux::TcpRateConnection to print.
 * @returns The output stream.
 */
std::ostream& operator<<(std::ostream& os, const TcpRateOps::TcpRateConnection& rate);

/**
 * Comparison operator
 * @param lhs left operand
 * @param rhs right operand
 * @return true if the operands are equal
 */
bool operator==(const TcpRateLinux::TcpRateSample& lhs, const TcpRateLinux::TcpRateSample& rhs);

/**
 * Comparison operator
 * @param lhs left operand
 * @param rhs right operand
 * @return true if the operands are equal
 */
bool operator==(const TcpRateLinux::TcpRateConnection& lhs,
                const TcpRateLinux::TcpRateConnection& rhs);

} // namespace ns3

#endif /* TCP_RATE_OPS_H */
