/* Copyright (c) 2017 - 2026 LiteSpeed Technologies Inc.  See LICENSE. */
/*
 * lsquic_pr_queue.c -- packet request queue.
 */

#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>

#ifndef WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#endif

#include <openssl/aead.h>
#include <openssl/rand.h>

#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_gquic.h"
#include "lsquic_packet_out.h"
#include "lsquic_packet_in.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_parse.h"
#include "lsquic_malo.h"
#include "lsquic_pr_queue.h"
#include "lsquic_parse_common.h"
#include "lsquic_tokgen.h"
#include "lsquic_version.h"
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lsquic_sizes.h"
#include "lsquic_handshake.h"
#include "lsquic_rapidhash.h"
#include "lsquic_crand.h"

#define LSQUIC_LOGGER_MODULE LSQLM_PRQ
#include "lsquic_logger.h"

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))


static const struct conn_iface evanescent_conn_iface;


struct packet_req
{
    struct lsquic_hash_elem     pr_hash_el;
    lsquic_cid_t                pr_scid;
    lsquic_cid_t                pr_dcid;
    enum packet_req_type        pr_type;
    enum pr_flags {
        PR_GQUIC    = 1 << 0,
    }                           pr_flags;
    enum lsquic_version         pr_version;
    unsigned                    pr_rst_sz;
    struct network_path         pr_path;
};


struct evanescent_conn
{
    struct lsquic_conn          evc_conn;
    struct packet_req          *evc_req;
    struct pr_queue            *evc_queue;
    struct lsquic_packet_out    evc_packet_out;
    struct conn_cid_elem        evc_cces[1];
    enum {
        EVC_DROP    = 1 << 0,
    }                           evc_flags;
    unsigned char               evc_buf[0];
};


/* [RFC 9000], Section 17.2.1 (Version Negotiation Packet) */
#define IQUIC_VERNEG_SIZE (1 /* Type */ + 4 /* Version (zero tag) */ \
                + 1 /* DCIL */ + MAX_CID_LEN + 1 /* SCIL */ + MAX_CID_LEN + \
                4 * N_LSQVER)

/* [RFC 9000], Section 17.2.5 (Retry Packet) */
#define IQUIC_RETRY_SIZE (1 /* Type */ + 4 /* Version */ + \
                + 1 /* DCIL */ + MAX_CID_LEN + 1 /* SCIL */ + MAX_CID_LEN + \
                + 1 /* ODCIL */ + MAX_CID_LEN + MAX_RETRY_TOKEN_LEN)

/* GQUIC retry is dynamically size, this is a reasonable high bound */
#define GQUIC_RETRY_SIZE 512

struct pr_queue
{
    TAILQ_HEAD(, lsquic_conn)   prq_free_conns,
                                prq_returned_conns;
    struct malo                *prq_reqs_pool;
    const struct lsquic_engine_public
                               *prq_enpub;
    struct lsquic_hash         *prq_reqs_hash;
    unsigned                    prq_max_reqs;
    unsigned                    prq_nreqs;
    unsigned                    prq_max_conns;
    unsigned                    prq_nconns;
    unsigned                    prq_verneg_g_sz;  /* Size of prq_verneg_g_buf */
    unsigned                    prq_pubres_g_sz;  /* Size of prq_pubres_g_buf */

    /* GQUIC version negotiation and stateless reset packets are generated
     * once, when the Packet Request Queue is created.  For each request,
     * these buffers are simply copied and the connection ID is replaced.
     *
     * Since IETF QUIC uses variable-length connections IDs, we have to
     * generate packets every time.
     */
    unsigned char               prq_pubres_g_buf[GQUIC_RESET_SZ];
    unsigned char               prq_verneg_g_buf[1 + GQUIC_CID_LEN
                                                                + N_LSQVER * 4];
};


static int
comp_reqs (const void *s1, const void *s2, size_t n)
{
    const struct packet_req *a, *b;

    a = s1;
    b = s2;
    if (a->pr_type == b->pr_type && LSQUIC_CIDS_EQ(&a->pr_dcid, &b->pr_dcid))
        return 0;
    else
        return -1;
}


static uint64_t
hash_req (const void *p, size_t len, uint64_t seed)
{
    const struct packet_req *req;

    req = p;
    return rapidhash_withSeed(req->pr_dcid.idbuf, req->pr_dcid.len, seed);
}


struct pr_queue *
lsquic_prq_create (unsigned max_elems, unsigned max_conns,
                        const struct lsquic_engine_public *enpub)
{
    const struct parse_funcs *pf;
    struct pr_queue *prq;
    struct malo *malo;
    struct lsquic_hash *hash;
    unsigned verneg_g_sz;
    ssize_t prst_g_sz;
    int len;

    malo = lsquic_malo_create(sizeof(struct packet_req));
    if (!malo)
    {
        LSQ_WARN("malo_create failed: %s", strerror(errno));
        goto err0;
    }


    hash = lsquic_hash_create_ext(comp_reqs, hash_req);
    if (!hash)
    {
        LSQ_WARN("cannot create hash");
        goto err1;
    }

    prq = malloc(sizeof(*prq));
    if (!prq)
    {
        LSQ_WARN("malloc failed: %s", strerror(errno));
        goto err2;
    }

    const lsquic_cid_t cid = { .len = 8, };
    pf = select_pf_by_ver(LSQVER_043);
    len = lsquic_gquic_gen_ver_nego_pkt(prq->prq_verneg_g_buf,
                    sizeof(prq->prq_verneg_g_buf), &cid,
                    enpub->enp_settings.es_versions);
    assert(len > 0);
    if (len <= 0)
    {
        LSQ_ERROR("cannot generate version negotiation packet");
        goto err3;
    }
    verneg_g_sz = (unsigned) len;

    prst_g_sz = pf->pf_generate_simple_prst(0 /* This is just placeholder */,
                                prq->prq_pubres_g_buf, sizeof(prq->prq_pubres_g_buf));
    if (prst_g_sz < 0)
    {
        LSQ_ERROR("cannot generate public reset packet");
        goto err3;
    }

    TAILQ_INIT(&prq->prq_free_conns);
    TAILQ_INIT(&prq->prq_returned_conns);
    prq->prq_reqs_hash = hash;
    prq->prq_reqs_pool = malo;
    prq->prq_max_reqs = max_elems;
    prq->prq_nreqs = 0;
    prq->prq_max_conns = max_conns;
    prq->prq_nconns = 0;
    prq->prq_verneg_g_sz = verneg_g_sz;
    prq->prq_pubres_g_sz = (unsigned) prst_g_sz;
    prq->prq_enpub       = enpub;

    LSQ_INFO("initialized queue of size %d", max_elems);

    return prq;

  err3:
    free(prq);
  err2:
    lsquic_hash_destroy(hash);
  err1:
    lsquic_malo_destroy(malo);
  err0:
    return NULL;
}


void
lsquic_prq_destroy (struct pr_queue *prq)
{
    struct lsquic_conn *conn;

    LSQ_INFO("destroy");
    while ((conn = TAILQ_FIRST(&prq->prq_free_conns)))
    {
        TAILQ_REMOVE(&prq->prq_free_conns, conn, cn_next_pr);
        free(conn);
    }
    lsquic_hash_destroy(prq->prq_reqs_hash);
    lsquic_malo_destroy(prq->prq_reqs_pool);
    free(prq);
}


static struct packet_req *
get_req (struct pr_queue *prq)
{
    struct packet_req *req;
    if (prq->prq_nreqs < prq->prq_max_reqs)
    {
        req = lsquic_malo_get(prq->prq_reqs_pool);
        if (req)
            ++prq->prq_nreqs;
        else
            LSQ_WARN("malo_get failed: %s", strerror(errno));
        return req;
    }
    else
        return NULL;
}


static void
put_req (struct pr_queue *prq, struct packet_req *req)
{
    lsquic_malo_put(req);
    --prq->prq_nreqs;
}


int
lsquic_prq_new_req_ext (struct pr_queue *prq, enum packet_req_type type,
    unsigned flags, enum lsquic_version version, unsigned short data_sz,
    const lsquic_cid_t *dcid, const lsquic_cid_t *scid, void *peer_ctx,
    const struct sockaddr *local_addr, const struct sockaddr *peer_addr)
{
    struct packet_req *req;
    unsigned max, size, rand;

    if (type == PACKET_REQ_PUBRES && !(flags & PR_GQUIC))
    {
        if (data_sz <= IQUIC_MIN_SRST_SIZE)
        {
            LSQ_DEBUGC("not scheduling public reset: incoming packet for CID "
                "%"CID_FMT" too small: %hu bytes", CID_BITS(dcid), data_sz);
            return -1;
        }
        /* Use a random stateless reset size */
        max = MIN(IQUIC_MAX_SRST_SIZE, data_sz - 1u);
        if (max > IQUIC_MIN_SRST_SIZE)
        {
            rand = lsquic_crand_get_byte(prq->prq_enpub->enp_crand);
            size = IQUIC_MIN_SRST_SIZE + rand % (max - IQUIC_MIN_SRST_SIZE);
        }
        else
            size = IQUIC_MIN_SRST_SIZE;
        LSQ_DEBUGC("selected %u-byte reset size for CID %"CID_FMT
            " (range is [%u, %u])", size, CID_BITS(dcid),
            IQUIC_MIN_SRST_SIZE, max);
    }
    else
        size = 0;

    req = get_req(prq);
    if (!req)
    {
        LSQ_DEBUG("out of reqs: cannot allocated another one");
        return -1;
    }

    req->pr_type     = type;
    req->pr_dcid     = *dcid;
    if (lsquic_hash_find(prq->prq_reqs_hash, req, sizeof(*req)))
    {
        LSQ_DEBUG("request for this DCID and type already exists");
        put_req(prq, req);
        return -1;
    }

    req->pr_hash_el.qhe_flags = 0;
    if (!lsquic_hash_insert(prq->prq_reqs_hash, req, sizeof(*req),
                                                    req, &req->pr_hash_el))
    {
        LSQ_DEBUG("could not insert req into hash");
        put_req(prq, req);
        return -1;
    }

    req->pr_flags    = flags;
    req->pr_rst_sz   = size;
    req->pr_version  = version;
    req->pr_scid     = *scid;
    req->pr_path.np_peer_ctx = peer_ctx;
    memcpy(req->pr_path.np_local_addr, local_addr,
                                            sizeof(req->pr_path.np_local_addr));
    memcpy(NP_PEER_SA(&req->pr_path), peer_addr,
                                            sizeof(req->pr_path.np_peer_addr));

    LSQ_DEBUGC("scheduled %s packet for connection %"CID_FMT,
                            lsquic_preqt2str[type], CID_BITS(&req->pr_dcid));
    return 0;
}


int
lsquic_prq_new_req (struct pr_queue *prq, enum packet_req_type type,
         const struct lsquic_packet_in *packet_in, void *peer_ctx,
         const struct sockaddr *local_addr, const struct sockaddr *peer_addr)
{
    lsquic_ver_tag_t ver_tag;
    enum lsquic_version version;
    enum pr_flags flags;
    lsquic_cid_t scid;

    if (packet_in->pi_flags & PI_GQUIC)
        flags = PR_GQUIC;
    else
        flags = 0;

    if (packet_in->pi_quic_ver)
    {
        memcpy(&ver_tag, packet_in->pi_data + packet_in->pi_quic_ver,
                                                            sizeof(ver_tag));
        version = lsquic_tag2ver(ver_tag);
    }
    else /* Got to set it to something sensible... */
        version = LSQVER_ID27;

    lsquic_scid_from_packet_in(packet_in, &scid);
    return lsquic_prq_new_req_ext(prq, type, flags, version,
                packet_in->pi_data_sz, &packet_in->pi_dcid, &scid,
                peer_ctx, local_addr, peer_addr);
}


static size_t
max_bufsz (const struct pr_queue *prq)
{
    return  MAX(MAX(MAX(MAX(IQUIC_VERNEG_SIZE,
                            IQUIC_RETRY_SIZE),
                            IQUIC_MAX_SRST_SIZE),
                            sizeof(prq->prq_verneg_g_buf)),
                            sizeof(prq->prq_pubres_g_buf));
}


static struct evanescent_conn *
get_evconn (struct pr_queue *prq)
{
    struct evanescent_conn *evconn;
    struct lsquic_conn *lconn;
    struct lsquic_packet_out *packet_out;
    size_t bufsz;

    if (prq->prq_nconns >= prq->prq_max_conns)
    {   /* This deserves a warning */
        LSQ_WARN("tried to get connection past limit of %u", prq->prq_max_conns);
        return NULL;
    }

    lconn = TAILQ_FIRST(&prq->prq_free_conns);
    if (lconn)
    {
        TAILQ_REMOVE(&prq->prq_free_conns, lconn, cn_next_pr);
        evconn = (struct evanescent_conn *) lconn;
        evconn->evc_flags = 0;
        return evconn;
    }

    bufsz = max_bufsz(prq);
    evconn = calloc(1, sizeof(*evconn) + bufsz);
    if (!evconn)
    {
        LSQ_WARN("calloc failed: %s", strerror(errno));
        return NULL;
    }

    /* These values stay the same between connection usages: */
    evconn->evc_queue = prq;
    lconn = &evconn->evc_conn;
    lconn->cn_cces = evconn->evc_cces;
    lconn->cn_cces_mask = 1;
    lconn->cn_n_cces = sizeof(evconn->evc_cces) / sizeof(evconn->evc_cces[0]);
    lconn->cn_if = &evanescent_conn_iface;
    lconn->cn_flags = LSCONN_EVANESCENT;
    packet_out = &evconn->evc_packet_out;
    packet_out->po_flags = PO_NOENCRYPT;
    packet_out->po_data = evconn->evc_buf;

    return evconn;
}


struct lsquic_conn *
lsquic_prq_next_conn (struct pr_queue *prq)
{
    struct evanescent_conn *evconn;
    struct lsquic_conn *lconn;
    struct lsquic_hash_elem *el;
    struct packet_req *req;
    struct lsquic_packet_out *packet_out;
    int (*gen_verneg) (unsigned char *, size_t, const lsquic_cid_t *,
                                    const lsquic_cid_t *, unsigned, uint8_t);
    int len;

    lconn = TAILQ_FIRST(&prq->prq_returned_conns);
    if (lconn)
    {
        TAILQ_REMOVE(&prq->prq_returned_conns, lconn, cn_next_pr);
        return lconn;
    }

    el = lsquic_hash_first(prq->prq_reqs_hash);
    if (!el)           /* Nothing is queued */
        return NULL;

    evconn = get_evconn(prq);
    if (!evconn)         /* Reached limit or malloc failed */
        return NULL;

    req = lsquic_hashelem_getdata(el);
    packet_out = &evconn->evc_packet_out;
    switch ((req->pr_type << 29) | req->pr_flags)
    {
    case (PACKET_REQ_VERNEG << 29) | PR_GQUIC:
        packet_out->po_data_sz = prq->prq_verneg_g_sz;
        packet_out->po_flags |= PO_VERNEG;
        memcpy(packet_out->po_data, prq->prq_verneg_g_buf,
                                                    prq->prq_verneg_g_sz);
        memcpy(packet_out->po_data + 1, req->pr_dcid.idbuf, GQUIC_CID_LEN);
        break;
    case (PACKET_REQ_PUBRES << 29) | PR_GQUIC:
        packet_out->po_flags &= ~PO_VERNEG;
        packet_out->po_data_sz = prq->prq_pubres_g_sz;
        memcpy(packet_out->po_data, prq->prq_pubres_g_buf,
                                                    prq->prq_pubres_g_sz);
        memcpy(packet_out->po_data + 1, req->pr_dcid.idbuf, GQUIC_CID_LEN);
        break;
    case (PACKET_REQ_VERNEG << 29) | 0:
        packet_out->po_flags |= PO_VERNEG;
        if (req->pr_version == LSQVER_046)
            gen_verneg = lsquic_Q046_gen_ver_nego_pkt;
        else
            gen_verneg = lsquic_ietf_v1_gen_ver_nego_pkt;
        len = gen_verneg(packet_out->po_data, max_bufsz(prq),
                    /* Flip SCID/DCID here: */ &req->pr_dcid, &req->pr_scid,
                    prq->prq_enpub->enp_settings.es_versions,
                    lsquic_crand_get_byte(prq->prq_enpub->enp_crand));
        if (len > 0)
            packet_out->po_data_sz = len;
        else
            packet_out->po_data_sz = 0;
        break;
    case (PACKET_REQ_RETRY << 29) | 0:
        packet_out->po_flags |= PO_RETRY;
        len = lsquic_iquic_gen_retry_pkt(packet_out->po_data, max_bufsz(prq),
                    prq->prq_enpub, &req->pr_scid, &req->pr_dcid,
                    req->pr_version, NP_PEER_SA(&req->pr_path),
                    lsquic_crand_get_nybble(prq->prq_enpub->enp_crand));
        if (len > 0)
            packet_out->po_data_sz = len;
        else
            packet_out->po_data_sz = 0;
        break;
    default:
        packet_out->po_flags &= ~PO_VERNEG;
        packet_out->po_data_sz = req->pr_rst_sz;
        RAND_bytes(packet_out->po_data, req->pr_rst_sz - IQUIC_SRESET_TOKEN_SZ);
        packet_out->po_data[0] &= ~0x80;
        packet_out->po_data[0] |=  0x40;
        lsquic_tg_generate_sreset(prq->prq_enpub->enp_tokgen, &req->pr_dcid,
            packet_out->po_data + req->pr_rst_sz - IQUIC_SRESET_TOKEN_SZ);
        break;
    }

    lsquic_hash_erase(prq->prq_reqs_hash, el);
    evconn->evc_req = req;

    lconn= &evconn->evc_conn;
    evconn->evc_cces[0].cce_cid = req->pr_dcid;
    packet_out->po_path = &req->pr_path;

    ++prq->prq_nconns;
    return lconn;
}


int
lsquic_prq_have_pending (const struct pr_queue *prq)
{
    return lsquic_hash_count(prq->prq_reqs_hash) > 0;
}


static struct lsquic_packet_out *
evanescent_conn_ci_next_packet_to_send (struct lsquic_conn *lconn,
                                        const struct to_coal *to_coal_UNUSED)
{
    struct evanescent_conn *const evconn = (struct evanescent_conn *) lconn;
    assert(!to_coal_UNUSED);
    return &evconn->evc_packet_out;
}


static void
prq_free_conn (struct pr_queue *prq, struct lsquic_conn *lconn)
{
    struct evanescent_conn *const evconn = (struct evanescent_conn *) lconn;

    TAILQ_INSERT_HEAD(&prq->prq_free_conns, lconn, cn_next_pr);
    put_req(prq, evconn->evc_req);
    --prq->prq_nconns;
}


static void
evanescent_conn_ci_packet_sent (struct lsquic_conn *lconn,
                            struct lsquic_packet_out *packet_out)
{
    struct evanescent_conn *const evconn = (struct evanescent_conn *) lconn;
    struct pr_queue *const prq = evconn->evc_queue;

    assert(packet_out == &evconn->evc_packet_out);
    assert(prq->prq_nconns > 0);

    LSQ_DEBUGC("sent %s packet for connection %"CID_FMT"; free resources",
        lsquic_preqt2str[ evconn->evc_req->pr_type ],
        CID_BITS(&evconn->evc_req->pr_dcid));
    prq_free_conn(prq, lconn);
}


static void
evanescent_conn_ci_packet_not_sent (struct lsquic_conn *lconn,
                                struct lsquic_packet_out *packet_out)
{
    struct evanescent_conn *const evconn = (struct evanescent_conn *) lconn;
    struct pr_queue *const prq = evconn->evc_queue;

    assert(packet_out == &evconn->evc_packet_out);
    assert(prq->prq_nconns > 0);

    if (evconn->evc_flags & EVC_DROP)
    {
        LSQ_DEBUGC("packet not sent; drop connection %"CID_FMT,
                                        CID_BITS(&evconn->evc_req->pr_dcid));
        prq_free_conn(prq, lconn);
    }
    else
    {
        LSQ_DEBUG("packet not sent; put connection onto used list");
        TAILQ_INSERT_HEAD(&prq->prq_returned_conns, lconn, cn_next_pr);
    }
}


static enum tick_st
evanescent_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
{
    assert(0);
    return TICK_CLOSE;
}


static void
evanescent_conn_ci_destroy (struct lsquic_conn *lconn)
{
    assert(0);
}


static struct lsquic_engine *
evanescent_conn_ci_get_engine (struct lsquic_conn *lconn)
{
    assert(0);
    return NULL;
}


static void
evanescent_conn_ci_hsk_done (struct lsquic_conn *lconn,
                                                enum lsquic_hsk_status status)
{
    assert(0);
}


static void
evanescent_conn_ci_packet_in (struct lsquic_conn *lconn,
                          struct lsquic_packet_in *packet_in)
{
    assert(0);
}


static void
evanescent_conn_ci_client_call_on_new (struct lsquic_conn *lconn)
{
    assert(0);
}


static struct network_path *
evanescent_conn_ci_get_path (struct lsquic_conn *lconn,
                                                    const struct sockaddr *sa)
{
    struct evanescent_conn *const evconn = (struct evanescent_conn *) lconn;

    return &evconn->evc_req->pr_path;
}


static unsigned char
evanescent_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx,
            const struct sockaddr *local_sa, const struct sockaddr *peer_sa)
{
    assert(0);
    return 0;
}


static const struct conn_iface evanescent_conn_iface = {
    .ci_client_call_on_new   =  evanescent_conn_ci_client_call_on_new,
    .ci_destroy              =  evanescent_conn_ci_destroy,
    .ci_get_engine           =  evanescent_conn_ci_get_engine,
    .ci_get_path             =  evanescent_conn_ci_get_path,
    .ci_hsk_done             =  evanescent_conn_ci_hsk_done,
    .ci_next_packet_to_send  =  evanescent_conn_ci_next_packet_to_send,
    .ci_packet_in            =  evanescent_conn_ci_packet_in,
    .ci_packet_not_sent      =  evanescent_conn_ci_packet_not_sent,
    .ci_packet_sent          =  evanescent_conn_ci_packet_sent,
    .ci_record_addrs         =  evanescent_conn_ci_record_addrs,
    .ci_tick                 =  evanescent_conn_ci_tick,
};


const char *const lsquic_preqt2str[] =
{
    [PACKET_REQ_VERNEG] = "version negotiation",
    [PACKET_REQ_PUBRES] = "stateless reset",
};


void
lsquic_prq_drop (struct lsquic_conn *lconn)
{
    struct evanescent_conn *const evconn = (void *) lconn;

    evconn->evc_flags |= EVC_DROP;
    LSQ_DEBUGC("mark for connection %"CID_FMT" for dropping",
                                        CID_BITS(&evconn->evc_req->pr_dcid));
}
