/* Copyright (c) 2017 - 2026 LiteSpeed Technologies Inc.  See LICENSE. */
#include <assert.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/queue.h>

#include "lsquic_int_types.h"
#include "lsquic_types.h"
#include "lsquic_hash.h"
#include "lsquic.h"
#include "lsquic_conn.h"
#include "lsquic_malo.h"
#include "lsquic_util.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_out.h"
#include "lsquic_parse.h"
#include "lsquic_bw_sampler.h"

#define LSQUIC_LOGGER_MODULE LSQLM_BW_SAMPLER
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(sampler->bws_conn)
#include "lsquic_logger.h"


int
lsquic_bw_sampler_init (struct bw_sampler *sampler, struct lsquic_conn *conn,
                                                enum quic_ft_bit retx_frames)
{
    struct malo *malo;

    assert(lsquic_is_zero(sampler, sizeof(*sampler)));

    malo = lsquic_malo_create(sizeof(struct bwp_state));
    if (!malo)
        return -1;

    sampler->bws_malo = malo;
    sampler->bws_conn = conn;
    sampler->bws_retx_frames = retx_frames;
    sampler->bws_flags |= BWS_APP_LIMITED;
    LSQ_DEBUG("init");
    return 0;
}


void
lsquic_bw_sampler_app_limited (struct bw_sampler *sampler)
{
    sampler->bws_flags |= BWS_APP_LIMITED;
    sampler->bws_end_of_app_limited_phase = sampler->bws_last_sent_packno;
    LSQ_DEBUG("app limited, end of limited phase is %"PRIu64,
                                    sampler->bws_end_of_app_limited_phase);
}


void
lsquic_bw_sampler_cleanup (struct bw_sampler *sampler)
{
    if (sampler->bws_conn)
        LSQ_DEBUG("cleanup");
    if (sampler->bws_malo)
    {
        lsquic_malo_destroy(sampler->bws_malo);
        sampler->bws_malo = NULL;
    }
    memset(sampler, 0, sizeof(*sampler));
}


/* This module only fails when it is unable to allocate memory.  This rarely
 * happens, so we avoid having to check return values and abort the connection
 * instead.
 */
static void
bw_sampler_abort_conn (struct bw_sampler *sampler)
{
    if (!(sampler->bws_flags & BWS_CONN_ABORTED))
    {
        sampler->bws_flags |= BWS_CONN_ABORTED;
        LSQ_WARN("aborting connection");
        sampler->bws_conn->cn_if->ci_internal_error(sampler->bws_conn,
                                                    "resources exhausted");
    }
}


#define BW_WARN_ONCE(...) do {                                              \
    if (!(sampler->bws_flags & BWS_WARNED))                                 \
    {                                                                       \
        sampler->bws_flags |= BWS_WARNED;                                   \
        LSQ_WARN(__VA_ARGS__);                                              \
    }                                                                       \
} while (0)

void
lsquic_bw_sampler_packet_sent (struct bw_sampler *sampler,
                    struct lsquic_packet_out *packet_out, uint64_t in_flight)
{
    struct bwp_state *state;
    unsigned short sent_sz;

    if (packet_out->po_bwp_state)
    {
        BW_WARN_ONCE("sent: packet %"PRIu64" already has state",
                                                        packet_out->po_packno);
        return;
    }

    sampler->bws_last_sent_packno = packet_out->po_packno;

    if (!(packet_out->po_frame_types & sampler->bws_retx_frames))
        return;

    sent_sz = lsquic_packet_out_sent_sz(sampler->bws_conn, packet_out);
    sampler->bws_total_sent += sent_sz;

    // If there are no packets in flight, the time at which the new transmission
    // opens can be treated as the A_0 point for the purpose of bandwidth
    // sampling. This underestimates bandwidth to some extent, and produces some
    // artificially low samples for most packets in flight, but it provides with
    // samples at important points where we would not have them otherwise, most
    // importantly at the beginning of the connection.
    if (in_flight == 0)
    {
        sampler->bws_last_acked_packet_time = packet_out->po_sent;
        sampler->bws_last_acked_total_sent = sampler->bws_total_sent;
        // In this situation ack compression is not a concern, set send rate to
        // effectively infinite.
        sampler->bws_last_acked_sent_time = packet_out->po_sent;
    }

    state = lsquic_malo_get(sampler->bws_malo);
    if (!state)
    {
        bw_sampler_abort_conn(sampler);
        return;
    }

    state->bwps_send_state = (struct bwps_send_state) {
        .total_bytes_sent   = sampler->bws_total_sent,
        .total_bytes_acked  = sampler->bws_total_acked,
        .total_bytes_lost   = sampler->bws_total_lost,
        .is_app_limited     = !!(sampler->bws_flags & BWS_APP_LIMITED),
    };
    state->bwps_sent_at_last_ack = sampler->bws_last_acked_total_sent;
    state->bwps_last_ack_sent_time = sampler->bws_last_acked_sent_time;
    state->bwps_last_ack_ack_time = sampler->bws_last_acked_packet_time;
    state->bwps_packet_size = sent_sz;

    packet_out->po_bwp_state = state;

    LSQ_DEBUG("add info for packet %"PRIu64, packet_out->po_packno);
}


void
lsquic_bw_sampler_packet_lost (struct bw_sampler *sampler,
                                    struct lsquic_packet_out *packet_out)
{
    if (!packet_out->po_bwp_state)
        return;

    sampler->bws_total_lost += packet_out->po_bwp_state->bwps_packet_size;
    lsquic_malo_put(packet_out->po_bwp_state);
    packet_out->po_bwp_state = NULL;
    LSQ_DEBUG("packet %"PRIu64" lost, total_lost goes to %"PRIu64,
                            packet_out->po_packno, sampler->bws_total_lost);
}


struct bw_sample *
lsquic_bw_sampler_packet_acked (struct bw_sampler *sampler,
                struct lsquic_packet_out *packet_out, lsquic_time_t ack_time)
{
    const struct bwp_state *state;
    struct bw_sample *sample;
    struct bandwidth send_rate, ack_rate;
    lsquic_time_t rtt;
    unsigned short sent_sz;
    int is_app_limited;

    if (!packet_out->po_bwp_state)
        return 0;

    state = packet_out->po_bwp_state;
    sent_sz = lsquic_packet_out_sent_sz(sampler->bws_conn, packet_out);

    sampler->bws_total_acked += sent_sz;
    sampler->bws_last_acked_total_sent = state->bwps_send_state.total_bytes_sent;
    sampler->bws_last_acked_sent_time = packet_out->po_sent;
    sampler->bws_last_acked_packet_time = ack_time;

    /* Update bandwidth calculation window: reset every ~1 second */
    if (sampler->bws_bw_calc_time == 0
        || ack_time - sampler->bws_bw_calc_time >= 1000000)
    {
        sampler->bws_bw_calc_acked = sampler->bws_total_acked;
        sampler->bws_bw_calc_time = ack_time;
    }

    // Exit app-limited phase once a packet that was sent while the connection
    // is not app-limited is acknowledged.
    if ((sampler->bws_flags & BWS_APP_LIMITED)
            && packet_out->po_packno > sampler->bws_end_of_app_limited_phase)
    {
        sampler->bws_flags &= ~BWS_APP_LIMITED;
        LSQ_DEBUG("exit app-limited phase due to packet %"PRIu64" being acked",
                                                        packet_out->po_packno);
    }

    // There might have been no packets acknowledged at the moment when the
    // current packet was sent. In that case, there is no bandwidth sample to
    // make.
    if (state->bwps_last_ack_sent_time == 0)
        goto no_sample;

    // Infinite rate indicates that the sampler is supposed to discard the
    // current send rate sample and use only the ack rate.
    if (packet_out->po_sent > state->bwps_last_ack_sent_time)
        send_rate = BW_FROM_BYTES_AND_DELTA(
            state->bwps_send_state.total_bytes_sent
                                    - state->bwps_sent_at_last_ack,
            packet_out->po_sent - state->bwps_last_ack_sent_time);
    else
        send_rate = BW_INFINITE();

    // During the slope calculation, ensure that ack time of the current packet is
    // always larger than the time of the previous packet, otherwise division by
    // zero or integer underflow can occur.
    if (ack_time <= state->bwps_last_ack_ack_time)
    {
        BW_WARN_ONCE("Time of the previously acked packet (%"PRIu64") is "
            "is larger than the ack time of the current packet (%"PRIu64")",
            state->bwps_last_ack_ack_time, ack_time);
        goto no_sample;
    }

    ack_rate = BW_FROM_BYTES_AND_DELTA(
        sampler->bws_total_acked - state->bwps_send_state.total_bytes_acked,
        ack_time - state->bwps_last_ack_ack_time);
    LSQ_DEBUG("send rate: %"PRIu64"; ack rate: %"PRIu64, send_rate.value,
                                                            ack_rate.value);

    // Note: this sample does not account for delayed acknowledgement time.
    // This means that the RTT measurements here can be artificially high,
    // especially on low bandwidth connections.
    rtt = ack_time - packet_out->po_sent;
    is_app_limited = state->bwps_send_state.is_app_limited;

    /* After this point, we switch `sample' to point to `state' and don't
     * reference `state' anymore.
     */
    sample = (void *) packet_out->po_bwp_state;
    packet_out->po_bwp_state = NULL;
    if (BW_VALUE(&send_rate) < BW_VALUE(&ack_rate))
        sample->bandwidth = send_rate;
    else
        sample->bandwidth = ack_rate;
    sample->rtt = rtt;
    sample->is_app_limited = is_app_limited;

    LSQ_DEBUG("packet %"PRIu64" acked, bandwidth: %"PRIu64" bps",
                        packet_out->po_packno, BW_VALUE(&sample->bandwidth));

    return sample;

  no_sample:
    lsquic_malo_put(packet_out->po_bwp_state);
    packet_out->po_bwp_state = NULL;
    return NULL;;
}


unsigned
lsquic_bw_sampler_entry_count (const struct bw_sampler *sampler)
{
    void *el;
    unsigned count;

    count = 0;
    for (el = lsquic_malo_first(sampler->bws_malo); el;
                                    el = lsquic_malo_next(sampler->bws_malo))
        ++count;

    return count;
}


uint64_t
lsquic_bw_sampler_get_bw (struct bw_sampler *sampler)
{
    lsquic_time_t time_delta, now;
    uint64_t bytes_delta, bw;

    if (sampler->bws_bw_calc_time == 0 || sampler->bws_last_acked_packet_time == 0)
        return 0;

    now = sampler->bws_last_acked_packet_time;
    time_delta = now - sampler->bws_bw_calc_time;

    /* Use at least 100ms window to avoid noise */
    if (time_delta < 100000)
        return sampler->bws_last_bw;

    bytes_delta = sampler->bws_total_acked - sampler->bws_bw_calc_acked;
    bw = bytes_delta * 8 * 1000000 / time_delta;

    sampler->bws_last_bw = bw;

    return bw;
}
