/* Copyright (c) 2017 - 2026 LiteSpeed Technologies Inc.  See LICENSE. */
/*
 * lsquic_mm.c -- Memory manager.
 */

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

#include "fiu-local.h"

#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_sizes.h"
#include "lsquic_malo.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_rtt.h"
#include "lsquic_packet_common.h"
#include "lsquic_mini_conn.h"
#include "lsquic_enc_sess.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_packet_gquic.h"
#include "lsquic_packet_in.h"
#include "lsquic_packet_out.h"
#include "lsquic_parse.h"
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lsquic_full_conn.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_sfcw.h"
#include "lsquic_stream.h"

#ifndef LSQUIC_LOG_POOL_STATS
#define LSQUIC_LOG_POOL_STATS 0
#endif

#if LSQUIC_LOG_POOL_STATS
#include "lsquic_logger.h"
#endif

#ifndef LSQUIC_USE_POOLS
#define LSQUIC_USE_POOLS 1
#endif

#define FAIL_NOMEM do { errno = ENOMEM; return NULL; } while (0)


struct packet_in_buf
{
    SLIST_ENTRY(packet_in_buf)  next_pib;
};

struct packet_out_buf
{
    SLIST_ENTRY(packet_out_buf) next_pob;
};

struct four_k_page
{
    SLIST_ENTRY(four_k_page)  next_fkp;
};

struct sixteen_k_page
{
    SLIST_ENTRY(sixteen_k_page)  next_skp;
};


int
lsquic_mm_init (struct lsquic_mm *mm)
{
#if LSQUIC_USE_POOLS
    int i;
#endif

    mm->acki = malloc(sizeof(*mm->acki));
    mm->malo.stream_frame = lsquic_malo_create(sizeof(struct stream_frame));
    mm->malo.frame_rec_arr = lsquic_malo_create(sizeof(struct frame_rec_arr));
    mm->malo.mini_conn = lsquic_malo_create(sizeof(struct mini_conn));
    mm->malo.mini_conn_ietf = lsquic_malo_create(sizeof(struct ietf_mini_conn));
    mm->malo.packet_in = lsquic_malo_create(sizeof(struct lsquic_packet_in));
    mm->malo.packet_out = lsquic_malo_create(sizeof(struct lsquic_packet_out));
    mm->malo.dcid_elem = lsquic_malo_create(sizeof(struct dcid_elem));
    mm->malo.stream_hq_frame
                        = lsquic_malo_create(sizeof(struct stream_hq_frame));
    mm->ack_str = malloc(MAX_ACKI_STR_SZ);
#if LSQUIC_USE_POOLS
    TAILQ_INIT(&mm->free_packets_in);
    for (i = 0; i < MM_N_OUT_BUCKETS; ++i)
        SLIST_INIT(&mm->packet_out_bufs[i]);
    for (i = 0; i < MM_N_IN_BUCKETS; ++i)
        SLIST_INIT(&mm->packet_in_bufs[i]);
    SLIST_INIT(&mm->four_k_pages);
    SLIST_INIT(&mm->sixteen_k_pages);
#endif
    if (mm->acki && mm->malo.stream_frame && mm->malo.frame_rec_arr
        && mm->malo.mini_conn && mm->malo.mini_conn_ietf && mm->malo.packet_in
        && mm->malo.packet_out && mm->malo.dcid_elem
        && mm->malo.stream_hq_frame && mm->ack_str)
    {
        return 0;
    }
    else
        return -1;
}


void
lsquic_mm_cleanup (struct lsquic_mm *mm)
{
#if LSQUIC_USE_POOLS
    int i;
    struct packet_out_buf *pob;
    struct packet_in_buf *pib;
    struct four_k_page *fkp;
    struct sixteen_k_page *skp;
#endif

    free(mm->acki);
    lsquic_malo_destroy(mm->malo.stream_hq_frame);
    lsquic_malo_destroy(mm->malo.dcid_elem);
    lsquic_malo_destroy(mm->malo.packet_in);
    lsquic_malo_destroy(mm->malo.packet_out);
    lsquic_malo_destroy(mm->malo.stream_frame);
    lsquic_malo_destroy(mm->malo.frame_rec_arr);
    lsquic_malo_destroy(mm->malo.mini_conn);
    lsquic_malo_destroy(mm->malo.mini_conn_ietf);
    free(mm->ack_str);

#if LSQUIC_USE_POOLS
    for (i = 0; i < MM_N_OUT_BUCKETS; ++i)
        while ((pob = SLIST_FIRST(&mm->packet_out_bufs[i])))
        {
            SLIST_REMOVE_HEAD(&mm->packet_out_bufs[i], next_pob);
            free(pob);
        }

    for (i = 0; i < MM_N_IN_BUCKETS; ++i)
        while ((pib = SLIST_FIRST(&mm->packet_in_bufs[i])))
        {
            SLIST_REMOVE_HEAD(&mm->packet_in_bufs[i], next_pib);
            free(pib);
        }

    while ((fkp = SLIST_FIRST(&mm->four_k_pages)))
    {
        SLIST_REMOVE_HEAD(&mm->four_k_pages, next_fkp);
        free(fkp);
    }

    while ((skp = SLIST_FIRST(&mm->sixteen_k_pages)))
    {
        SLIST_REMOVE_HEAD(&mm->sixteen_k_pages, next_skp);
        free(skp);
    }
#endif
}


#if LSQUIC_USE_POOLS
enum {
    PACKET_IN_PAYLOAD_0 = 1370,     /* common QUIC payload size upperbound */
    PACKET_IN_PAYLOAD_1 = 4096,     /* payload size middleground guess */
    PACKET_IN_PAYLOAD_2 = 0xffff,   /* UDP payload size upperbound */
};


static const unsigned packet_in_sizes[] = {
    PACKET_IN_PAYLOAD_0,
    PACKET_IN_PAYLOAD_1,
    PACKET_IN_PAYLOAD_2,
};


static unsigned
packet_in_index (unsigned size)
{
    unsigned idx = (size > PACKET_IN_PAYLOAD_0)
                 + (size > PACKET_IN_PAYLOAD_1);
    return idx;
}
#endif


void
lsquic_mm_put_packet_in (struct lsquic_mm *mm,
                                        struct lsquic_packet_in *packet_in)
{
    assert(0 == packet_in->pi_refcnt);
    if (packet_in->pi_flags & PI_OWN_DATA)
    {
        lsquic_mm_put_packet_in_buf(mm, packet_in->pi_data, packet_in->pi_data_sz);
    }

#if LSQUIC_USE_POOLS
    TAILQ_INSERT_HEAD(&mm->free_packets_in, packet_in, pi_next);
#else
    lsquic_malo_put(packet_in);
#endif
}


struct lsquic_packet_in *
lsquic_mm_get_packet_in (struct lsquic_mm *mm)
{
    struct lsquic_packet_in *packet_in;

    fiu_do_on("mm/packet_in", FAIL_NOMEM);

#if LSQUIC_USE_POOLS
    packet_in = TAILQ_FIRST(&mm->free_packets_in);
    if (packet_in)
    {
        assert(0 == packet_in->pi_refcnt);
        TAILQ_REMOVE(&mm->free_packets_in, packet_in, pi_next);
    }
    else
#endif
        packet_in = lsquic_malo_get(mm->malo.packet_in);

    if (packet_in)
        memset(packet_in, 0, sizeof(*packet_in));

    return packet_in;
}


#if LSQUIC_USE_POOLS
/* Based on commonly used MTUs, ordered from small to large: */
enum {
    PACKET_OUT_PAYLOAD_0 = 1280                    - GQUIC_MIN_PACKET_OVERHEAD,
    PACKET_OUT_PAYLOAD_1 = GQUIC_MAX_IPv6_PACKET_SZ - GQUIC_MIN_PACKET_OVERHEAD,
    PACKET_OUT_PAYLOAD_2 = GQUIC_MAX_IPv4_PACKET_SZ - GQUIC_MIN_PACKET_OVERHEAD,
    PACKET_OUT_PAYLOAD_3 = 4096,
    PACKET_OUT_PAYLOAD_4 = 0xffff,
};


static const unsigned packet_out_sizes[] = {
    PACKET_OUT_PAYLOAD_0,
    PACKET_OUT_PAYLOAD_1,
    PACKET_OUT_PAYLOAD_2,
    PACKET_OUT_PAYLOAD_3,
    PACKET_OUT_PAYLOAD_4,
};


static unsigned
packet_out_index (unsigned size)
{
    unsigned idx = (size > PACKET_OUT_PAYLOAD_0)
                 + (size > PACKET_OUT_PAYLOAD_1)
                 + (size > PACKET_OUT_PAYLOAD_2)
                 + (size > PACKET_OUT_PAYLOAD_3);
    return idx;
}
#endif

#if LSQUIC_USE_POOLS
#define POOL_SAMPLE_PERIOD 1024

static void
poolst_sample_max (struct pool_stats *poolst)
{
#define ALPHA_SHIFT 3
#define BETA_SHIFT  2
    unsigned diff;

    if (poolst->ps_max_avg)
    {
        poolst->ps_max_var -= poolst->ps_max_var >> BETA_SHIFT;
        if (poolst->ps_max_avg > poolst->ps_max)
            diff = poolst->ps_max_avg - poolst->ps_max;
        else
            diff = poolst->ps_max - poolst->ps_max_avg;
        poolst->ps_max_var += diff >> BETA_SHIFT;
        poolst->ps_max_avg -= poolst->ps_max_avg >> ALPHA_SHIFT;
        poolst->ps_max_avg += poolst->ps_max >> ALPHA_SHIFT;
    }
    else
    {
        /* First measurement */
        poolst->ps_max_avg  = poolst->ps_max;
        poolst->ps_max_var  = poolst->ps_max / 2;
    }

    poolst->ps_calls = 0;
    poolst->ps_max = poolst->ps_objs_out;
#if LSQUIC_LOG_POOL_STATS
    LSQ_DEBUG("new sample: max avg: %u; var: %u", poolst->ps_max_avg,
                                                        poolst->ps_max_var);
#endif
}


static void
poolst_allocated (struct pool_stats *poolst, unsigned new)
{
    poolst->ps_objs_out += 1;
    poolst->ps_objs_all += new;
    if (poolst->ps_objs_out > poolst->ps_max)
        poolst->ps_max = poolst->ps_objs_out;
    ++poolst->ps_calls;
    if (0 == poolst->ps_calls % POOL_SAMPLE_PERIOD)
        poolst_sample_max(poolst);
}


static void
poolst_freed (struct pool_stats *poolst)
{
    --poolst->ps_objs_out;
    ++poolst->ps_calls;
    if (0 == poolst->ps_calls % POOL_SAMPLE_PERIOD)
        poolst_sample_max(poolst);
}


static int
poolst_has_new_sample (const struct pool_stats *poolst)
{
    return poolst->ps_calls == 0;
}


/* If average maximum falls under 1/4 of all objects allocated, release
 * half of the objects allocated.
 */
static void
maybe_shrink_packet_out_bufs (struct lsquic_mm *mm, unsigned idx)
{
    struct pool_stats *poolst;
    struct packet_out_buf *pob;
    unsigned n_to_leave;

    poolst = &mm->packet_out_bstats[idx];
    if (poolst->ps_max_avg * 4 < poolst->ps_objs_all)
    {
        n_to_leave = poolst->ps_objs_all / 2;
        while (poolst->ps_objs_all > n_to_leave
                        && (pob = SLIST_FIRST(&mm->packet_out_bufs[idx])))
        {
            SLIST_REMOVE_HEAD(&mm->packet_out_bufs[idx], next_pob);
            free(pob);
            --poolst->ps_objs_all;
        }
#if LSQUIC_LOG_POOL_STATS
        LSQ_DEBUG("pool #%u; max avg %u; shrank from %u to %u objs",
                idx, poolst->ps_max_avg, n_to_leave * 2, poolst->ps_objs_all);
#endif
    }
#if LSQUIC_LOG_POOL_STATS
    else
        LSQ_DEBUG("pool #%u; max avg %u; objs: %u; won't shrink",
                                idx, poolst->ps_max_avg, poolst->ps_objs_all);
#endif
}
#endif


#if LSQUIC_USE_POOLS
/* If average maximum falls under 1/4 of all objects allocated, release
 * half of the objects allocated.
 */
static void
maybe_shrink_packet_in_bufs (struct lsquic_mm *mm, unsigned idx)
{
    struct pool_stats *poolst;
    struct packet_in_buf *pib;
    unsigned n_to_leave;

    poolst = &mm->packet_in_bstats[idx];
    if (poolst->ps_max_avg * 4 < poolst->ps_objs_all)
    {
        n_to_leave = poolst->ps_objs_all / 2;
        while (poolst->ps_objs_all > n_to_leave
                        && (pib = SLIST_FIRST(&mm->packet_in_bufs[idx])))
        {
            SLIST_REMOVE_HEAD(&mm->packet_in_bufs[idx], next_pib);
            free(pib);
            --poolst->ps_objs_all;
        }
#if LSQUIC_LOG_POOL_STATS
        LSQ_DEBUG("pib pool #%u; max avg %u; shrank from %u to %u objs",
                idx, poolst->ps_max_avg, n_to_leave * 2, poolst->ps_objs_all);
#endif
    }
#if LSQUIC_LOG_POOL_STATS
    else
    {
        LSQ_DEBUG("pib pool #%u; max avg %u; objs: %u; won't shrink",
                                idx, poolst->ps_max_avg, poolst->ps_objs_all);
    }
#endif
}
#endif


void
lsquic_mm_put_packet_out (struct lsquic_mm *mm,
                          struct lsquic_packet_out *packet_out)
{
#if LSQUIC_USE_POOLS
    struct packet_out_buf *pob;
    unsigned idx;

    assert(packet_out->po_data);
    pob = (struct packet_out_buf *) packet_out->po_data;
    idx = packet_out_index(packet_out->po_n_alloc);
    SLIST_INSERT_HEAD(&mm->packet_out_bufs[idx], pob, next_pob);
    poolst_freed(&mm->packet_out_bstats[idx]);
    if (poolst_has_new_sample(&mm->packet_out_bstats[idx]))
        maybe_shrink_packet_out_bufs(mm, idx);
#else
    free(packet_out->po_data);
#endif
    lsquic_malo_put(packet_out);
}


struct lsquic_packet_out *
lsquic_mm_get_packet_out (struct lsquic_mm *mm, struct malo *malo,
                          unsigned short size)
{
    struct lsquic_packet_out *packet_out;
    struct packet_out_buf *pob;
#if LSQUIC_USE_POOLS
    unsigned idx;
#endif

    fiu_do_on("mm/packet_out", FAIL_NOMEM);

    packet_out = lsquic_malo_get(malo ? malo : mm->malo.packet_out);
    if (!packet_out)
        return NULL;

#if LSQUIC_USE_POOLS
    idx = packet_out_index(size);
    pob = SLIST_FIRST(&mm->packet_out_bufs[idx]);
    if (pob)
    {
        SLIST_REMOVE_HEAD(&mm->packet_out_bufs[idx], next_pob);
        poolst_allocated(&mm->packet_out_bstats[idx], 0);
    }
    else
    {
        pob = malloc(packet_out_sizes[idx]);
        if (!pob)
        {
            lsquic_malo_put(packet_out);
            return NULL;
        }
        poolst_allocated(&mm->packet_out_bstats[idx], 1);
    }
    if (poolst_has_new_sample(&mm->packet_out_bstats[idx]))
        maybe_shrink_packet_out_bufs(mm, idx);
#else
    pob = malloc(size);
    if (!pob)
    {
        lsquic_malo_put(packet_out);
        return NULL;
    }
#endif

    memset(packet_out, 0, sizeof(*packet_out));
    packet_out->po_n_alloc = size;
    packet_out->po_data = (unsigned char *) pob;

    return packet_out;
}


void *
lsquic_mm_get_packet_in_buf (struct lsquic_mm *mm, size_t size)
{
    struct packet_in_buf *pib;
#if LSQUIC_USE_POOLS
    unsigned idx;

    idx = packet_in_index(size);
    pib = SLIST_FIRST(&mm->packet_in_bufs[idx]);
    fiu_do_on("mm/packet_in_buf", FAIL_NOMEM);
    if (pib)
    {
        SLIST_REMOVE_HEAD(&mm->packet_in_bufs[idx], next_pib);
        poolst_allocated(&mm->packet_in_bstats[idx], 0);
    }
    else
    {
        pib = malloc(packet_in_sizes[idx]);
        if (!pib)
        {
            return NULL;
        }

        poolst_allocated(&mm->packet_in_bstats[idx], 1);
    }

    if (poolst_has_new_sample(&mm->packet_in_bstats[idx]))
    {
        maybe_shrink_packet_in_bufs(mm, idx);
    }
#else
    pib = malloc(size);
#endif
    return pib;
}


void
lsquic_mm_put_packet_in_buf (struct lsquic_mm *mm, void *mem, size_t size)
{
#if LSQUIC_USE_POOLS
    unsigned idx;
    struct packet_in_buf *pib;

    pib = (struct packet_in_buf *) mem;
    idx = packet_in_index(size);
    SLIST_INSERT_HEAD(&mm->packet_in_bufs[idx], pib, next_pib);

    poolst_freed(&mm->packet_in_bstats[idx]);
    if (poolst_has_new_sample(&mm->packet_in_bstats[idx]))
    {
        maybe_shrink_packet_in_bufs(mm, idx);
    }
#else
    free(mem);
#endif
}


void *
lsquic_mm_get_4k (struct lsquic_mm *mm)
{
#if LSQUIC_USE_POOLS
    struct four_k_page *fkp = SLIST_FIRST(&mm->four_k_pages);
    fiu_do_on("mm/4k", FAIL_NOMEM);
    if (fkp)
        SLIST_REMOVE_HEAD(&mm->four_k_pages, next_fkp);
    else
        fkp = malloc(0x1000);
    return fkp;
#else
    return malloc(0x1000);
#endif
}


void
lsquic_mm_put_4k (struct lsquic_mm *mm, void *mem)
{
#if LSQUIC_USE_POOLS
    struct four_k_page *fkp = mem;
    SLIST_INSERT_HEAD(&mm->four_k_pages, fkp, next_fkp);
#else
    free(mem);
#endif
}


void *
lsquic_mm_get_16k (struct lsquic_mm *mm)
{
#if LSQUIC_USE_POOLS
    struct sixteen_k_page *skp = SLIST_FIRST(&mm->sixteen_k_pages);
    fiu_do_on("mm/16k", FAIL_NOMEM);
    if (skp)
        SLIST_REMOVE_HEAD(&mm->sixteen_k_pages, next_skp);
    else
        skp = malloc(16 * 1024);
    return skp;
#else
    return malloc(16 * 1024);
#endif
}


void
lsquic_mm_put_16k (struct lsquic_mm *mm, void *mem)
{
#if LSQUIC_USE_POOLS
    struct sixteen_k_page *skp = mem;
    SLIST_INSERT_HEAD(&mm->sixteen_k_pages, skp, next_skp);
#else
    free(mem);
#endif
}


size_t
lsquic_mm_mem_used (const struct lsquic_mm *mm)
{
#if LSQUIC_USE_POOLS
    const struct packet_out_buf *pob;
    const struct packet_in_buf *pib;
    const struct four_k_page *fkp;
    const struct sixteen_k_page *skp;
    unsigned i;
    size_t size;

    size = sizeof(*mm);
    size += sizeof(*mm->acki);
    size += lsquic_malo_mem_used(mm->malo.stream_frame);
    size += lsquic_malo_mem_used(mm->malo.frame_rec_arr);
    size += lsquic_malo_mem_used(mm->malo.mini_conn);
    size += lsquic_malo_mem_used(mm->malo.mini_conn_ietf);
    size += lsquic_malo_mem_used(mm->malo.packet_in);
    size += lsquic_malo_mem_used(mm->malo.packet_out);

    for (i = 0; i < MM_N_OUT_BUCKETS; ++i)
        SLIST_FOREACH(pob, &mm->packet_out_bufs[i], next_pob)
            size += packet_out_sizes[i];

    for (i = 0; i < MM_N_IN_BUCKETS; ++i)
        SLIST_FOREACH(pib, &mm->packet_in_bufs[i], next_pib)
            size += packet_in_sizes[i];

    SLIST_FOREACH(fkp, &mm->four_k_pages, next_fkp)
        size += 0x1000;

    SLIST_FOREACH(skp, &mm->sixteen_k_pages, next_skp)
        size += 0x4000;

    return size;
#else
    return sizeof(*mm);
#endif
}
