/* Copyright (c) 2017 - 2026 LiteSpeed Technologies Inc.  See LICENSE. */
/*
 * lsquic_mini_conn_ietf.c -- Mini connection used by the IETF QUIC
 */

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

#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_sizes.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_mm.h"
#include "lsquic_malo.h"
#include "lsquic_engine_public.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_in.h"
#include "lsquic_packet_out.h"
#include "lsquic_parse.h"
#include "lsquic_rtt.h"
#include "lsquic_util.h"
#include "lsquic_enc_sess.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_ev_log.h"
#include "lsquic_trans_params.h"
#include "lsquic_ietf.h"
#include "lsquic_packet_ietf.h"
#include "lsquic_attq.h"
#include "lsquic_alarmset.h"
#include "lsquic_crand.h"

#define LSQUIC_LOGGER_MODULE LSQLM_MINI_CONN
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(&conn->imc_conn)
#include "lsquic_logger.h"

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

static const struct conn_iface mini_conn_ietf_iface;

static unsigned highest_bit_set (unsigned long long);

static void
ietf_mini_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app,
                                unsigned error_code, const char *fmt, ...);

static const enum header_type el2hety[] =
{
    [ENC_LEV_HSK]  = HETY_HANDSHAKE,
    [ENC_LEV_INIT] = HETY_INITIAL,
    [ENC_LEV_APP]  = HETY_SHORT,
    [ENC_LEV_0RTT] = 0,    /* Invalid */
};


static inline void
imico_destroy_packet (struct ietf_mini_conn *conn,
                                        struct lsquic_packet_out *packet_out)
{
    lsquic_packet_out_destroy(packet_out, conn->imc_enpub,
                                                conn->imc_path.np_peer_ctx);
}


int
lsquic_mini_conn_ietf_ecn_ok (const struct ietf_mini_conn *conn)
{
    packno_set_t acked;

    /* First flight has only Initial and Handshake packets */
    acked = conn->imc_acked_packnos[PNS_INIT]
          | conn->imc_acked_packnos[PNS_HSK]
          ;
    return 0 != (conn->imc_ecn_packnos & acked);
}


static inline enum ecn
imico_get_ecn (struct ietf_mini_conn *conn)
{
    if (!conn->imc_enpub->enp_settings.es_ecn)
        return ECN_NOT_ECT;
    else
        return ECN_ECT0;
}


static struct lsquic_packet_out *
imico_get_packet_out (struct ietf_mini_conn *conn,
                                    enum header_type header_type, size_t need)
{
    struct lsquic_packet_out *packet_out;
    enum ecn ecn;

    if (need)
        TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
            if (!(packet_out->po_flags & PO_SENT)
                    && packet_out->po_header_type == header_type
                    && lsquic_packet_out_avail(packet_out) >= need)
                return packet_out;

    if (conn->imc_next_packno >= MAX_PACKETS)
    {
        LSQ_DEBUG("ran out of outgoing packet numbers, won't allocate packet");
        return NULL;
    }

    packet_out = lsquic_packet_out_new(&conn->imc_enpub->enp_mm, NULL, 1,
            &conn->imc_conn, IQUIC_PACKNO_LEN_1, NULL, NULL, &conn->imc_path,
            header_type);
    if (!packet_out)
    {
        LSQ_WARN("could not allocate packet: %s", strerror(errno));
        return NULL;
    }

    packet_out->po_header_type = header_type;
    packet_out->po_packno = conn->imc_next_packno++;
    packet_out->po_flags |= PO_MINI;
    lsquic_packet_out_set_pns(packet_out, lsquic_hety2pns[header_type]);
    ecn = imico_get_ecn(conn);
    packet_out->po_lflags |= ecn << POECN_SHIFT;
    TAILQ_INSERT_TAIL(&conn->imc_packets_out, packet_out, po_next);
    packet_out->po_loss_chain = packet_out;
    return packet_out;
}


static inline struct ietf_mini_conn *
cryst_get_conn (const struct mini_crypto_stream *cryst)
{
    return (void *)
        ((unsigned char *) (cryst - cryst->mcs_enc_level)
                        - offsetof(struct ietf_mini_conn, imc_streams));
}


struct msg_ctx
{
    const unsigned char       *buf;
    const unsigned char *const end;
};


static size_t
read_from_msg_ctx (void *ctx, void *buf, size_t len, int *fin)
{
    struct msg_ctx *msg_ctx = ctx;
    if (len > (uintptr_t) (msg_ctx->end - msg_ctx->buf))
        len = msg_ctx->end - msg_ctx->buf;
    memcpy(buf, msg_ctx->buf, len);
    msg_ctx->buf += len;
    return len;
}


static inline int
imico_chlo_has_been_consumed (const struct ietf_mini_conn *conn)
{
    return conn->imc_streams[ENC_LEV_INIT].mcs_read_off > 3
        && conn->imc_streams[ENC_LEV_INIT].mcs_read_off >= conn->imc_ch_len;
}


static int
imico_process_version_negociation (struct ietf_mini_conn *conn,
                                   const struct transport_params *params)
{
    unsigned common_versions;
    unsigned ver;
    int i;
    if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
    {
        LSQ_DEBUG("client chosen version %s",
                    lsquic_ver2str[params->tp_chosen_version]);
        for (i = 1; i < params->tp_version_cnt; ++i)
        {
            LSQ_DEBUG("client available version %s",
                      lsquic_ver2str[params->tp_version_info[i]]);
            //EV_LOG_VER_NEG(LSQUIC_LOG_CONN_ID, "supports",
            //               lsquic_ver2str[params->tp_version_info[i]]);
        }
    }
    if (params->tp_chosen_version != conn->imc_conn.cn_version)
    {
        LSQ_DEBUG("client chosen version does not match the version in use %s",
                    lsquic_ver2str[conn->imc_conn.cn_version]);
        ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
                                      TEC_VERSION_NEGOTIATION_ERROR,
            "chosen version mismatch");
        return -1;
    }
    common_versions = conn->imc_enpub->enp_settings.es_versions
                & params->tp_versions;
    ver = highest_bit_set(common_versions);
    if (conn->imc_conn.cn_version != (enum lsquic_version)ver)
    {
        LSQ_DEBUG("version negociation: switch version from %s to %s",
                  lsquic_ver2str[conn->imc_conn.cn_version],
                  lsquic_ver2str[ver]);
        conn->imc_conn.cn_version = ver;
        conn->imc_conn.cn_flags |= LSCONN_VER_UPDATED;
        iquic_esfi_switch_version(conn->imc_conn.cn_enc_session,
                                  &conn->imc_cces[0].cce_cid, 1);
    }

    return 0;
}


static int
imico_maybe_process_params (struct ietf_mini_conn *conn)
{
    const struct transport_params *params;

    if (imico_chlo_has_been_consumed(conn)
        && (conn->imc_flags & (IMC_ENC_SESS_INITED|IMC_HAVE_TP))
                                                    == IMC_ENC_SESS_INITED)
    {
        params = conn->imc_conn.cn_esf.i->esfi_get_peer_transport_params(
                                                conn->imc_conn.cn_enc_session);
        if (params)
        {
            conn->imc_flags |= IMC_HAVE_TP;
            conn->imc_ack_exp = params->tp_ack_delay_exponent;
            if (params->tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE))
            {
                if (params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE]
                                                < conn->imc_path.np_pack_size)
                    conn->imc_path.np_pack_size =
                                params->tp_numerics[TPI_MAX_UDP_PAYLOAD_SIZE];
            }
            LSQ_DEBUG("read transport params, packet size is set to %hu bytes",
                                                conn->imc_path.np_pack_size);
            if (params->tp_set & (1 << TPI_VERSION_INFORMATION))
            {
                if (imico_process_version_negociation(conn, params) == -1)
                {
                    conn->imc_flags |= IMC_VER_NEG_FAILED;
                    return -1;
                }
            }
            iquic_esfi_init_server_tp(conn->imc_conn.cn_enc_session);
        }
        else
        {
            conn->imc_flags |= IMC_BAD_TRANS_PARAMS;
            return -1;
        }
    }

    return 0;
}


static inline void
imico_zero_pad (struct lsquic_packet_out *packet_out, size_t pad_size)
{
    memset(packet_out->po_data + packet_out->po_data_sz, 0, pad_size);
    packet_out->po_padding_sz = pad_size;
    packet_out->po_data_sz += pad_size;
    packet_out->po_frame_types |= QUIC_FTBIT_PADDING;
}


static int
imico_generate_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
                                                            lsquic_time_t now);

static ssize_t
imico_stream_write (void *stream, const void *bufp, size_t bufsz)
{
    struct mini_crypto_stream *const cryst = stream;
    struct ietf_mini_conn *const conn = cryst_get_conn(cryst);
    struct lsquic_conn *const lconn = &conn->imc_conn;
    const struct parse_funcs *const pf = lconn->cn_pf;
    struct msg_ctx msg_ctx = { bufp, (unsigned char *) bufp + bufsz, };
    struct lsquic_packet_out *packet_out;
    size_t header_sz, need;
    const unsigned char *p;
    int len;

    if (0 != imico_maybe_process_params(conn))
        return -1;

    if (PNS_INIT == lsquic_enclev2pns[ cryst->mcs_enc_level ]
                                        && (conn->imc_flags & IMC_IGNORE_INIT))
    {
        LSQ_WARN("trying to write at the ignored Initial level");
        return bufsz;
    }

    if (cryst->mcs_enc_level == ENC_LEV_INIT
        && (conn->imc_flags & IMC_QUEUED_ACK_INIT))
    {
        imico_generate_ack(conn, PNS_INIT, lsquic_time_now());
    }

    while (msg_ctx.buf < msg_ctx.end)
    {
        header_sz = lconn->cn_pf->pf_calc_crypto_frame_header_sz(
                            cryst->mcs_write_off, msg_ctx.end - msg_ctx.buf);
        need = header_sz + 500;
        packet_out = imico_get_packet_out(conn,
                                        el2hety[ cryst->mcs_enc_level ], need);
        if (!packet_out)
            return -1;
        // NOTE: reduce the size of first crypto frame to combine packets
        unsigned avail = lsquic_packet_out_avail(packet_out);
        int coalescing = 0;
        if (cryst->mcs_enc_level == ENC_LEV_HSK
            && cryst->mcs_write_off == 0
            && avail > (unsigned)conn->imc_hello_pkt_remain - conn->imc_long_header_sz)
        {
            avail = conn->imc_hello_pkt_remain - conn->imc_long_header_sz;
            conn->imc_hello_pkt_remain = 0;
            coalescing = 1;
        }
        p = msg_ctx.buf;
        len = pf->pf_gen_crypto_frame(packet_out->po_data + packet_out->po_data_sz,
                    avail, 0, cryst->mcs_write_off, 0,
                    msg_ctx.end - msg_ctx.buf, read_from_msg_ctx, &msg_ctx);
        if (len < 0)
            return len;
        EV_LOG_GENERATED_CRYPTO_FRAME(LSQUIC_LOG_CONN_ID, pf,
                                packet_out->po_data + packet_out->po_data_sz, len);
        packet_out->po_data_sz += len;
        packet_out->po_frame_types |= 1 << QUIC_FRAME_CRYPTO;
        packet_out->po_flags |= PO_HELLO;
        if (coalescing && len < (int)avail)
        {
            LSQ_DEBUG("generated PADDING frame: %d bytes for packet %"PRIu64,
                         avail - len, packet_out->po_packno);
            imico_zero_pad(packet_out, avail - len);
        }
        cryst->mcs_write_off += msg_ctx.buf - p;
        if (cryst->mcs_enc_level == ENC_LEV_INIT)
            conn->imc_hello_pkt_remain = lsquic_packet_out_avail(packet_out);
    }

    assert(msg_ctx.buf == msg_ctx.end);
    return bufsz;
}


static int
imico_stream_flush (void *stream)
{
    return 0;
}


static struct stream_frame *
imico_find_stream_frame (const struct ietf_mini_conn *conn,
                                enum enc_level enc_level, unsigned read_off)
{
    struct stream_frame *frame;

    if (conn->imc_last_in.frame && enc_level == conn->imc_last_in.enc_level
            && read_off == DF_ROFF(conn->imc_last_in.frame))
        return conn->imc_last_in.frame;

    TAILQ_FOREACH(frame, &conn->imc_crypto_frames, next_frame)
        if (enc_level == frame->stream_id && read_off == DF_ROFF(frame))
            return frame;

    return NULL;
}


static void
imico_read_chlo_size (struct ietf_mini_conn *conn, const unsigned char *buf,
                                                                    size_t sz)
{
    const unsigned char *const end = buf + sz;

    assert(conn->imc_streams[ENC_LEV_INIT].mcs_read_off < 4);
    switch (conn->imc_streams[ENC_LEV_INIT].mcs_read_off)
    {
    case 0:
        if (buf == end)
            return;
        if (*buf != 1)
        {
            LSQ_DEBUG("Does not begin with ClientHello");
            conn->imc_flags |= IMC_ERROR;
            return;
        }
        ++buf;
        /* fall-through */
    case 1:
        if (buf == end)
            return;
        if (*buf != 0)
        {
            LSQ_DEBUG("ClientHello larger than 16K");
            conn->imc_flags |= IMC_ERROR;
            return;
        }
        ++buf;
        /* fall-through */
    case 2:
        if (buf == end)
            return;
        conn->imc_ch_len = *buf << 8;
        ++buf;
        /* fall-through */
    default:
        if (buf == end)
            return;
        conn->imc_ch_len |= *buf;
    }
}


static ssize_t
imico_stream_readf (void *stream,
        size_t (*readf)(void *, const unsigned char *, size_t, int), void *ctx)
{
    struct mini_crypto_stream *const cryst = stream;
    struct ietf_mini_conn *const conn = cryst_get_conn(cryst);
    struct stream_frame *frame;
    const unsigned char *buf;
    size_t nread, total_read;
    unsigned avail;

    total_read = 0;
    while ((frame = imico_find_stream_frame(conn, cryst->mcs_enc_level,
                                                        cryst->mcs_read_off)))
    {
        avail = DF_SIZE(frame) - frame->data_frame.df_read_off;
        buf = frame->data_frame.df_data + frame->data_frame.df_read_off;
        nread = readf(ctx, buf, avail, DF_FIN(frame));
        if (cryst->mcs_enc_level == ENC_LEV_INIT && cryst->mcs_read_off < 4)
            imico_read_chlo_size(conn, buf, nread);
        total_read += nread;
        cryst->mcs_read_off += nread;
        frame->data_frame.df_read_off += nread;
        LSQ_DEBUG("read %zu bytes at offset %"PRIu64" on enc level %u", nread,
            DF_ROFF(frame), cryst->mcs_enc_level);
        if (DF_END(frame) == DF_ROFF(frame))
        {
            if (frame == conn->imc_last_in.frame)
                conn->imc_last_in.frame = NULL;
            else
            {
                TAILQ_REMOVE(&conn->imc_crypto_frames, frame, next_frame);
                --conn->imc_n_crypto_frames;
                conn->imc_crypto_frames_sz -= DF_SIZE(frame);
                lsquic_packet_in_put(&conn->imc_enpub->enp_mm,
                                                            frame->packet_in);
                lsquic_malo_put(frame);
            }
        }
        if (nread < avail)
            break;
    }

    if (total_read > 0)
        return total_read;
    else
    {
        /* CRYPTO streams never end, so zero bytes read always means
         * EWOULDBLOCK
         */
        errno = EWOULDBLOCK;
        return -1;
    }
}


static inline int
imico_stream_wantX (struct mini_crypto_stream *cryst, int bit, int is_want)
{
    int old;

    old = (cryst->mcs_flags & (1 << bit)) > 0;
    cryst->mcs_flags &= ~(1 << bit);
    cryst->mcs_flags |= !!is_want << bit;
    return old;
}


static int
imico_stream_wantwrite (void *stream, int is_want)
{
    return imico_stream_wantX(stream, MCSBIT_WANTWRITE, is_want);
}


static int
imico_stream_wantread (void *stream, int is_want)
{
    return imico_stream_wantX(stream, MCSBIT_WANTREAD, is_want);
}


static enum enc_level
imico_stream_enc_level (void *stream)
{
    struct mini_crypto_stream *const cryst = stream;
    return cryst->mcs_enc_level;
}


static const struct crypto_stream_if crypto_stream_if =
{
    .csi_write      = imico_stream_write,
    .csi_flush      = imico_stream_flush,
    .csi_readf      = imico_stream_readf,
    .csi_wantwrite  = imico_stream_wantwrite,
    .csi_wantread   = imico_stream_wantread,
    .csi_enc_level  = imico_stream_enc_level,
};


static inline int
is_first_packet_ok (const struct lsquic_packet_in *packet_in,
                                                    size_t udp_payload_size)
{
    if (udp_payload_size < IQUIC_MIN_INIT_PACKET_SZ)
    {
        /* [RFC 9000] Section 14.1 (Initial Datagram Size) */
        LSQ_LOG1(LSQ_LOG_DEBUG, "incoming UDP payload too small: %zu bytes",
                                                            udp_payload_size);
        return 0;
    }
    /* TODO: Move decryption of the first packet into this function? */
    return 1;   /* TODO */
}


static inline void
imico_peer_addr_validated (struct ietf_mini_conn *conn, const char *how)
{
    if (!(conn->imc_flags & IMC_ADDR_VALIDATED))
    {
        conn->imc_flags |= IMC_ADDR_VALIDATED;
        LSQ_DEBUG("peer address validated (%s)", how);
    }
}


struct lsquic_conn *
lsquic_mini_conn_ietf_new (struct lsquic_engine_public *enpub,
                           struct lsquic_packet_in *packet_in,
           enum lsquic_version version, int is_ipv4, const lsquic_cid_t *odcid,
           size_t udp_payload_size)
{
    struct ietf_mini_conn *conn;
    enc_session_t *enc_sess;
    enum enc_level i;
    const struct enc_session_funcs_iquic *esfi;
    unsigned char rand_nybble;

    if (!is_first_packet_ok(packet_in, udp_payload_size))
        return NULL;
    packet_in->pi_flags |= PI_FIRST_INIT;
    conn = lsquic_malo_get(enpub->enp_mm.malo.mini_conn_ietf);
    if (!conn)
    {
        LSQ_LOG1(LSQ_LOG_WARN, "cannot allocate mini connection: %s",
                                                            strerror(errno));
        return NULL;
    }
    memset(conn, 0, sizeof(*conn));
    conn->imc_conn.cn_if = &mini_conn_ietf_iface;
    conn->imc_conn.cn_cces = conn->imc_cces;
    conn->imc_conn.cn_n_cces = sizeof(conn->imc_cces)
                                                / sizeof(conn->imc_cces[0]);
    conn->imc_cces[0].cce_cid = packet_in->pi_dcid;
    conn->imc_cces[0].cce_flags = CCE_USED | CCE_REG;
    conn->imc_conn.cn_cces_mask = 1;
    lsquic_scid_from_packet_in(packet_in, &conn->imc_path.np_dcid);
    /* Generate new SCID. Since is not the original SCID, it is given
     * a sequence number (0) and therefore can be retired by the client.
     */
    conn->imc_conn.cn_cces[1].cce_cid.len = enpub->enp_settings.es_scid_len;
    enpub->enp_generate_scid(enpub->enp_gen_scid_ctx, &conn->imc_conn,
        conn->imc_conn.cn_cces[1].cce_cid.buf, enpub->enp_settings.es_scid_len);

    if (conn->imc_path.np_dcid.len)
        conn->imc_conn.cn_logid = conn->imc_path.np_dcid;
    else
        conn->imc_conn.cn_logid = conn->imc_conn.cn_cces[1].cce_cid;

    LSQ_DEBUGC("recv SCID from client %"CID_FMT, CID_BITS(&conn->imc_cces[0].cce_cid));
    LSQ_DEBUGC("recv DCID from client %"CID_FMT, CID_BITS(&conn->imc_path.np_dcid));
    LSQ_DEBUGC("generated SCID %"CID_FMT" at index %u, switching to it",
                CID_BITS(&conn->imc_conn.cn_cces[1].cce_cid), 1);
    conn->imc_conn.cn_cces[1].cce_flags = CCE_SEQNO | CCE_USED;
    conn->imc_conn.cn_cces_mask |= 1u << 1;
    conn->imc_conn.cn_cur_cce_idx = 1;

    conn->imc_conn.cn_flags = LSCONN_MINI|LSCONN_IETF|LSCONN_SERVER;
    conn->imc_conn.cn_version = version;

    for (i = 0; i < N_ENC_LEVS; ++i)
    {
        conn->imc_streams[i].mcs_enc_level = i;
        conn->imc_stream_ps[i] = &conn->imc_streams[i];
    }

    rand_nybble = lsquic_crand_get_nybble(enpub->enp_crand);
    if (rand_nybble == 0)
    {
        /* Use trechist for about one out of every sixteen connections so
         * that the code does not grow stale.
         */
        LSQ_DEBUG("using trechist");
        conn->imc_flags |= IMC_TRECHIST;
        conn->imc_recvd_packnos.trechist.hist_elems
                                    = malloc(TRECHIST_SIZE * IMICO_N_PNS);
        if (!conn->imc_recvd_packnos.trechist.hist_elems)
        {
            LSQ_WARN("cannot allocate trechist elems");
            return NULL;
        }
    }

    esfi = &lsquic_enc_session_iquic_ietf_v1;
    if (version > LSQVER_ID27)
        enc_sess = esfi->esfi_create_server(enpub, &conn->imc_conn,
                    &packet_in->pi_dcid, conn->imc_stream_ps, &crypto_stream_if,
                    odcid ? odcid : &conn->imc_cces[0].cce_cid,
                    &conn->imc_path.np_dcid,
                    odcid ? &conn->imc_cces[0].cce_cid : NULL);
    else
        enc_sess = esfi->esfi_create_server(enpub, &conn->imc_conn,
                    &packet_in->pi_dcid, conn->imc_stream_ps, &crypto_stream_if,
                    odcid, &conn->imc_path.np_dcid, NULL);
    if (!enc_sess)
    {
        lsquic_malo_put(conn);
        return NULL;
    }

    conn->imc_enpub = enpub;
    conn->imc_expire = packet_in->pi_received +
                            enpub->enp_settings.es_handshake_to;
    if (enpub->enp_settings.es_base_plpmtu)
        conn->imc_path.np_pack_size = enpub->enp_settings.es_base_plpmtu;
    else if (is_ipv4)
        conn->imc_path.np_pack_size = IQUIC_MAX_IPv4_PACKET_SZ;
    else
        conn->imc_path.np_pack_size = IQUIC_MAX_IPv6_PACKET_SZ;
    conn->imc_conn.cn_pf = select_pf_by_ver(version);
    conn->imc_conn.cn_esf.i = esfi;
    conn->imc_conn.cn_enc_session = enc_sess;
    conn->imc_conn.cn_esf_c = &lsquic_enc_session_common_ietf_v1;
    TAILQ_INIT(&conn->imc_packets_out);
    TAILQ_INIT(&conn->imc_app_packets);
    TAILQ_INIT(&conn->imc_crypto_frames);
    if (odcid)
        imico_peer_addr_validated(conn, "odcid");
#if LSQUIC_DEVEL
    {
        const char *const s = getenv("LSQUIC_LOSE_0RTT");
        if (s && atoi(s))
        {
            LSQ_DEBUG("will lose 0-RTT packets (via env variable)");
            conn->imc_delayed_packets_count = UCHAR_MAX;
        }
    }
#endif

    conn->imc_long_header_sz = 1 /* Type */
       + 4 /* Version */
       + 1 /* DCIL */
       + conn->imc_path.np_dcid.len
       + 1 /* SCIL */
       + CN_SCID(&conn->imc_conn)->len
       + 0 /* HSK packet by server has no token */
       + 2 /* Always use two bytes to encode payload length */
       + 1 /* mini conn packet number < 256 */
       + IQUIC_TAG_LEN
       ;
    conn->imc_hello_pkt_remain = conn->imc_path.np_pack_size
                - conn->imc_long_header_sz - 1 /* token len */;

    LSQ_DEBUG("created mini connection object %p; max packet size=%hu",
                                     conn, conn->imc_path.np_pack_size);
    return &conn->imc_conn;
}


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


static void
ietf_mini_conn_ci_destroy (struct lsquic_conn *lconn)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    struct lsquic_packet_out *packet_out;
    struct lsquic_packet_in *packet_in;
    struct stream_frame *frame;

    while ((packet_out = TAILQ_FIRST(&conn->imc_packets_out)))
    {
        TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
        imico_destroy_packet(conn, packet_out);
    }
    while ((packet_in = TAILQ_FIRST(&conn->imc_app_packets)))
    {
        TAILQ_REMOVE(&conn->imc_app_packets, packet_in, pi_next);
        lsquic_packet_in_put(&conn->imc_enpub->enp_mm, packet_in);
    }
    while ((frame = TAILQ_FIRST(&conn->imc_crypto_frames)))
    {
        TAILQ_REMOVE(&conn->imc_crypto_frames, frame, next_frame);
        lsquic_packet_in_put(&conn->imc_enpub->enp_mm, frame->packet_in);
        lsquic_malo_put(frame);
    }
    if (lconn->cn_enc_session)
        lconn->cn_esf.i->esfi_destroy(lconn->cn_enc_session);
    LSQ_DEBUG("ietf_mini_conn_ci_destroyed");
    if (conn->imc_flags & IMC_TRECHIST)
        free(conn->imc_recvd_packnos.trechist.hist_elems);
    lsquic_malo_put(conn);
}


static struct lsquic_engine *
ietf_mini_conn_ci_get_engine (struct lsquic_conn *lconn)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    return conn->imc_enpub->enp_engine;
}


static void
ietf_mini_conn_ci_hsk_done (struct lsquic_conn *lconn,
                                                enum lsquic_hsk_status status)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;

    switch (status)
    {
    case LSQ_HSK_OK:
    case LSQ_HSK_RESUMED_OK:
        conn->imc_flags |= IMC_HSK_OK;
        conn->imc_conn.cn_flags |= LSCONN_HANDSHAKE_DONE;
        LSQ_DEBUG("handshake OK");
        break;
    default:
        assert(0);
        /* fall-through */
    case LSQ_HSK_FAIL:
        conn->imc_flags |= IMC_HSK_FAILED|IMC_ERROR;
        LSQ_INFO("handshake failed");
        break;
    }
}


static void
ietf_mini_conn_ci_tls_alert (struct lsquic_conn *lconn, uint8_t alert)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    LSQ_DEBUG("got TLS alert %"PRIu8, alert);
    conn->imc_flags |= IMC_ERROR|IMC_TLS_ALERT;
    conn->imc_tls_alert = alert;
}


static inline int
imico_can_send (const struct ietf_mini_conn *conn, size_t size)
{
    return (conn->imc_flags & IMC_ADDR_VALIDATED)
        || conn->imc_bytes_in * conn->imc_enpub->enp_settings.es_amp_factor
                        >= conn->imc_bytes_out + size
        ;
}


/* A mini connection is only tickable if it has unsent packets.  This can
 * occur when packet sending is delayed.
 *
 * Otherwise, a mini connection is not tickable:  Either there are incoming
 * packets, in which case, the connection is going to be ticked, or there is
 * an alarm pending, in which case it will be handled via the attq.
 */
static int
ietf_mini_conn_ci_is_tickable (struct lsquic_conn *lconn)
{
    struct ietf_mini_conn *const conn = (struct ietf_mini_conn *) lconn;
    const struct lsquic_packet_out *packet_out;
    size_t packet_size;

    if ((conn->imc_enpub->enp_flags & ENPUB_CAN_SEND)
        && !(conn->imc_flags & IMC_AMP_CAPPED))
        TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
            if (!(packet_out->po_flags & PO_SENT))
            {
                packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
                return imico_can_send(conn, packet_size);
            }

    return 0;
}


static lsquic_time_t
imico_rechist_largest_recv (void *rechist_ctx);


static int
imico_build_ack_frame (struct ietf_mini_conn *conn, enum packnum_space pns,
                       lsquic_time_t now, unsigned char *outbuf,
                       size_t outbuf_sz, lsquic_packno_t *ack2ed)
{
    struct ietf_mini_rechist rechist;
    uint64_t ecn_counts_buf[4];
    const uint64_t *ecn_counts;
    int not_used_has_missing, len;
    if (conn->imc_incoming_ecn)
    {
        ecn_counts_buf[0]   = conn->imc_ecn_counts_in[pns][0];
        ecn_counts_buf[1]   = conn->imc_ecn_counts_in[pns][1];
        ecn_counts_buf[2]   = conn->imc_ecn_counts_in[pns][2];
        ecn_counts_buf[3]   = conn->imc_ecn_counts_in[pns][3];
        ecn_counts = ecn_counts_buf;
    }
    else
        ecn_counts = NULL;
    lsquic_imico_rechist_init(&rechist, conn, pns);
    len = conn->imc_conn.cn_pf->pf_gen_ack_frame(outbuf, outbuf_sz,
                lsquic_imico_rechist_first,
                lsquic_imico_rechist_next, imico_rechist_largest_recv, &rechist,
                now, &not_used_has_missing, ack2ed, ecn_counts);
    if (len < 0)
    {
        LSQ_WARN("could not generate ACK frame");
        return -1;
    }
    EV_LOG_GENERATED_ACK_FRAME(LSQUIC_LOG_CONN_ID, conn->imc_conn.cn_pf,
                               outbuf, len);
    return len;
}


static void
remove_ack_frame (struct ietf_mini_conn *conn,
                   struct lsquic_packet_out *packet_out)
{
    if (packet_out->po_regen_sz == 0)
        return;

    int l = packet_out->po_data_sz - packet_out->po_regen_sz
            - packet_out->po_padding_sz;
    memmove(packet_out->po_data ,
            packet_out->po_data + packet_out->po_regen_sz, l);
    memset(packet_out->po_data + l, 0, packet_out->po_regen_sz);
    packet_out->po_padding_sz += packet_out->po_regen_sz;
    packet_out->po_regen_sz = 0;
}


// static void
// update_packet_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
//                    struct lsquic_packet_out *packet_out)
// {
//     int len, need;
//     unsigned char outbuf[1024];
//     size_t outbuf_sz = sizeof(outbuf);
//     lsquic_packno_t ack2ed;
//
//     len = imico_build_ack_frame(conn, pns, lsquic_time_now(), outbuf,
//                       outbuf_sz, &ack2ed);
//     if (len > packet_out->po_regen_sz + packet_out->po_padding_sz)
//         return;
//     need = len + !!(packet_out->po_frame_types & (1 << QUIC_FRAME_PING));
//     LSQ_DEBUG("update leading ACK frame for packet %"PRIu64
//               ", regen_sz: %hd, padding_sz: %hd, need: %d, len: %d",
//               packet_out->po_packno, packet_out->po_regen_sz,
//               packet_out->po_padding_sz, need, len);
//
//     if (need != packet_out->po_regen_sz)
//     {
//         int l = packet_out->po_data_sz - packet_out->po_regen_sz
//                 - packet_out->po_padding_sz;
//         memmove(packet_out->po_data + need,
//                 packet_out->po_data + packet_out->po_regen_sz, l);
//         if (packet_out->po_padding_sz)
//         {
//             if (need < packet_out->po_regen_sz)
//                 memset(packet_out->po_data + need + l, 0,
//                     packet_out->po_regen_sz - need);
//             packet_out->po_padding_sz += packet_out->po_regen_sz - need;
//         }
//         else
//             packet_out->po_data_sz -= packet_out->po_regen_sz - need;
//         packet_out->po_regen_sz = need;
//     }
//     memmove(packet_out->po_data, outbuf, len);
//     packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK;
//     packet_out->po_ack2ed = ack2ed;
// }


static struct lsquic_packet_out *
ietf_mini_conn_ci_next_packet_to_send (struct lsquic_conn *lconn,
                                            const struct to_coal *to_coal)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    struct lsquic_packet_out *packet_out;
    size_t packet_size;

    TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
    {
        if (packet_out->po_flags & PO_SENT)
            continue;
        /* [RFC 9000] Section 14.1 (Initial Datagram Size):
         " A server MUST expand the payload of all UDP datagrams carrying
         " ack-eliciting Initial packets to at least the smallest allowed
         " maximum datagram size of 1200 bytes.
         */
        if (packet_out->po_header_type == HETY_INITIAL)
        {
            if (packet_out->po_frame_types & (1 << QUIC_FRAME_ACK)
                && packet_out->po_retx_cnt > 0)
            {
                remove_ack_frame(conn, packet_out);
            }

            if (packet_out->po_retx_cnt > 0
                && !imico_can_send(conn, IQUIC_MAX_IPv4_PACKET_SZ))
            {
                conn->imc_flags |= IMC_AMP_CAPPED;
                LSQ_DEBUG("cannot send INIT packet #%"PRIu64" without "
                          "enough quota", packet_out->po_packno);
                return NULL;
            }
            // NOTE: do not padd INIT packet only, here, instead pad the coalesced
            //       later, can save one packet, more efficient.
            // if (!(packet_out->po_frame_types & (1 << QUIC_FRAME_PADDING))
            //     && (packet_out->po_frame_types & IQUIC_FRAME_ACKABLE_MASK)
            //     && IQUIC_MIN_INIT_PACKET_SZ > packet_out->po_data_sz)
            // {
            //     size_t pad_size;
            //
            //     pad_size = IQUIC_MIN_INIT_PACKET_SZ - packet_out->po_data_sz;
            //     LSQ_DEBUG("generated PADDING frame: %zd bytes for packet %"PRIu64,
            //             pad_size, packet_out->po_packno);
            //     imico_zero_pad(packet_out, pad_size);
            // }
        }
        packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
        if (!(to_coal
            && (packet_size + to_coal->prev_sz_sum
                                            > conn->imc_path.np_pack_size
            || !lsquic_packet_out_equal_dcids(to_coal->prev_packet, packet_out))
            ))
        {
            if (!imico_can_send(conn, packet_size))
            {
                conn->imc_flags |= IMC_AMP_CAPPED;
                LSQ_DEBUG("cannot send packet %"PRIu64" of size %zu: client "
                    "address has not been validated", packet_out->po_packno,
                    packet_size);
                return NULL;
            }
            packet_out->po_flags |= PO_SENT;
            conn->imc_bytes_out += packet_size;
            if (!to_coal)
                LSQ_DEBUG("packet_to_send: %"PRIu64, packet_out->po_packno);
            else
                LSQ_DEBUG("packet_to_send: %"PRIu64" (coalesced)",
                                                    packet_out->po_packno);
            return packet_out;
        }
        else
            return NULL;
    }

    return NULL;
}


static int
imico_calc_retx_timeout (const struct ietf_mini_conn *conn)
{
    lsquic_time_t to;
    to = lsquic_rtt_stats_get_srtt(&conn->imc_rtt_stats);
    if (to)
    {
        to += to / 2;
        if (to < 10000)
            to = 10000;
    }
    else
        to = 300000;
    return to;
}


static lsquic_time_t
ietf_mini_conn_ci_next_tick_time (struct lsquic_conn *lconn, unsigned *why)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    const struct lsquic_packet_out *packet_out;
    lsquic_time_t exp_time, retx_time;

    exp_time = conn->imc_expire;

    TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
        if (packet_out->po_flags & PO_SENT)
        {
            retx_time = packet_out->po_sent + (imico_calc_retx_timeout(conn)
                                            << (packet_out->po_retx_cnt >> 1));
            if (retx_time < exp_time)
            {
                *why = N_AEWS + AL_RETX_HSK;
                return retx_time;
            }
            else
                break;
        }

    *why = AEW_MINI_EXPIRE;
    return exp_time;
}


#define IMICO_PROC_FRAME_ARGS                                           \
    struct ietf_mini_conn *conn, struct lsquic_packet_in *packet_in,    \
    const unsigned char *p, size_t len


static void
imico_dispatch_stream_events (struct ietf_mini_conn *conn)
{
    enum enc_level i;

    for (i = 0; i < N_ENC_LEVS; ++i)
        if ((conn->imc_streams[i].mcs_flags & (MCS_CREATED|MCS_WANTREAD))
                                                == (MCS_CREATED|MCS_WANTREAD))
        {
            LSQ_DEBUG("dispatch read events on level #%u", i);
            lsquic_mini_cry_sm_if.on_read((void *) &conn->imc_streams[i],
                                            conn->imc_conn.cn_enc_session);
        }

    for (i = 0; i < N_ENC_LEVS; ++i)
        if ((conn->imc_streams[i].mcs_flags & (MCS_CREATED|MCS_WANTWRITE))
                                                == (MCS_CREATED|MCS_WANTWRITE))
        {
            LSQ_DEBUG("dispatch write events on level #%u", i);
            lsquic_mini_cry_sm_if.on_write((void *) &conn->imc_streams[i],
                                            conn->imc_conn.cn_enc_session);
        }
}


static int
imico_stash_stream_frame (struct ietf_mini_conn *conn,
        enum enc_level enc_level, struct lsquic_packet_in *packet_in,
        const struct stream_frame *frame)
{
    struct stream_frame *copy;

    if (conn->imc_n_crypto_frames >= IMICO_MAX_STASHED_FRAMES)
    {
        LSQ_INFO("cannot stash more CRYPTO frames, at %hhu already, while max "
            "is %u", conn->imc_n_crypto_frames, IMICO_MAX_STASHED_FRAMES);
        return -1;
    }

    if (conn->imc_crypto_frames_sz + DF_SIZE(frame) > IMICO_MAX_BUFFERED_CRYPTO)
    {
        LSQ_INFO("cannot stash more than %u bytes of CRYPTO frames",
            IMICO_MAX_BUFFERED_CRYPTO);
        return -1;
    }

    copy = lsquic_malo_get(conn->imc_enpub->enp_mm.malo.stream_frame);
    if (!copy)
    {
        LSQ_INFO("could not allocate stream frame for stashing");
        return -1;
    }

    *copy = *frame;
    copy->packet_in = lsquic_packet_in_get(packet_in);
    copy->stream_id = enc_level;
    TAILQ_INSERT_TAIL(&conn->imc_crypto_frames, copy, next_frame);
    ++conn->imc_n_crypto_frames;
    conn->imc_crypto_frames_sz += DF_SIZE(frame);
    return 0;
}


static unsigned
imico_process_crypto_frame (IMICO_PROC_FRAME_ARGS)
{
    int parsed_len;
    enum enc_level enc_level, i;
    struct stream_frame stream_frame;

    parsed_len = conn->imc_conn.cn_pf->pf_parse_crypto_frame(p, len,
                                                                &stream_frame);
    if (parsed_len < 0)
    {
        conn->imc_flags |= IMC_PARSE_FAILED;
        return 0;
    }

    enc_level = lsquic_packet_in_enc_level(packet_in);
    EV_LOG_CRYPTO_FRAME_IN(LSQUIC_LOG_CONN_ID, &stream_frame, enc_level);

    if (conn->imc_streams[enc_level].mcs_read_off >= DF_OFF(&stream_frame)
        && conn->imc_streams[enc_level].mcs_read_off < DF_END(&stream_frame))
        LSQ_DEBUG("Got CRYPTO frame for enc level #%u", enc_level);
    else if (conn->imc_streams[enc_level].mcs_read_off < DF_OFF(&stream_frame))
    {
        LSQ_DEBUG("Can't read CRYPTO frame on enc level #%u at offset %"PRIu64
            " yet -- stash", enc_level, DF_OFF(&stream_frame));
        if (0 == imico_stash_stream_frame(conn, enc_level, packet_in,
                                                                &stream_frame))
            return parsed_len;
        else
            return 0;
    }
    else
    {
        LSQ_DEBUG("Got duplicate CRYPTO frame for enc level #%u -- ignore",
                                                                    enc_level);
        return parsed_len;
    }

    if (!(conn->imc_flags & IMC_ENC_SESS_INITED))
    {
        if (0 != conn->imc_conn.cn_esf.i->esfi_init_server(
                                            conn->imc_conn.cn_enc_session))
            return 0;
        conn->imc_flags |= IMC_ENC_SESS_INITED;
    }

    if (!(conn->imc_streams[enc_level].mcs_flags & MCS_CREATED))
    {
        LSQ_DEBUG("creating stream on level #%u", enc_level);
        conn->imc_streams[enc_level].mcs_flags |= MCS_CREATED;
        lsquic_mini_cry_sm_if.on_new_stream(conn->imc_conn.cn_enc_session,
                                    (void *) &conn->imc_streams[enc_level]);
    }

    /* Assume that receiving a CRYPTO frame at a higher level means that we
     * no longer want to read from a lower level.
     */
    for (i = 0; i < enc_level; ++i)
        conn->imc_streams[i].mcs_flags &= ~MCS_WANTREAD;

    conn->imc_last_in.frame = &stream_frame;
    conn->imc_last_in.enc_level = enc_level;
    imico_dispatch_stream_events(conn);
    conn->imc_last_in.frame = NULL;

    if (DF_ROFF(&stream_frame) < DF_END(&stream_frame))
    {
        /* This is an odd condition, but let's handle it just in case */
        LSQ_DEBUG("New CRYPTO frame on enc level #%u not fully read -- stash",
            enc_level);
        if (0 != imico_stash_stream_frame(conn, enc_level, packet_in,
                                                                &stream_frame))
            return 0;
    }


    return parsed_len;
}


static ptrdiff_t
imico_count_zero_bytes (const unsigned char *p, size_t len)
{
    const unsigned char *const end = p + len;
    while (p < end && 0 == *p)
        ++p;
    return len - (end - p);
}


static unsigned
imico_process_padding_frame (IMICO_PROC_FRAME_ARGS)
{
    len = (size_t) imico_count_zero_bytes(p, len);
    EV_LOG_PADDING_FRAME_IN(LSQUIC_LOG_CONN_ID, len);
    return len;
}


static void
imico_take_rtt_sample (struct ietf_mini_conn *conn,
                            const struct lsquic_packet_out *packet_out,
                            lsquic_time_t now, lsquic_time_t lack_delta)
{
    assert(packet_out->po_sent);
    lsquic_time_t measured_rtt = now - packet_out->po_sent;
    if (lack_delta < measured_rtt)
    {
        lsquic_rtt_stats_update(&conn->imc_rtt_stats, measured_rtt, lack_delta);
        LSQ_DEBUG("srtt: %"PRIu64" usec, var: %"PRIu64,
                        lsquic_rtt_stats_get_srtt(&conn->imc_rtt_stats),
                        lsquic_rtt_stats_get_rttvar(&conn->imc_rtt_stats));
    }
}


static unsigned
imico_process_ack_frame (IMICO_PROC_FRAME_ARGS)
{
    int parsed_len;
    unsigned n;
    lsquic_packet_out_t *packet_out, *next;
    struct ack_info *acki;
    lsquic_packno_t packno;
    lsquic_time_t warn_time;
    packno_set_t acked;
    enum packnum_space pns;
    uint8_t ack_exp;

    if (conn->imc_flags & IMC_HAVE_TP)
        ack_exp = conn->imc_ack_exp;
    else
        ack_exp = TP_DEF_ACK_DELAY_EXP; /* Odd: no transport params yet? */
    acki = conn->imc_enpub->enp_mm.acki;
    parsed_len = conn->imc_conn.cn_pf->pf_parse_ack_frame(p, len, acki,
                                                                    ack_exp);
    if (parsed_len < 0)
    {
        conn->imc_flags |= IMC_PARSE_FAILED;
        return 0;
    }

    pns = lsquic_hety2pns[ packet_in->pi_header_type ];
    acked = 0;

    for (n = 0; n < acki->n_ranges; ++n)
    {
        if (acki->ranges[n].high <= MAX_PACKETS)
        {
            acked |= (1ULL << acki->ranges[n].high)
                                        | ((1ULL << acki->ranges[n].high) - 1);
            acked &= ~((1ULL << acki->ranges[n].low) - 1);
        }
        else
        {
            packno = acki->ranges[n].high;
            goto err_never_sent;
        }
    }
    if (acked & ~conn->imc_sent_packnos)
    {
        packno = highest_bit_set(acked & ~conn->imc_sent_packnos);
        goto err_never_sent;
    }

    EV_LOG_ACK_FRAME_IN(LSQUIC_LOG_CONN_ID, acki);
    for (packet_out = TAILQ_FIRST(&conn->imc_packets_out); packet_out;
                                                            packet_out = next)
    {
        next = TAILQ_NEXT(packet_out, po_next);
        if ((1ULL << packet_out->po_packno) & acked)
        {
            if (lsquic_packet_out_pns(packet_out) != pns)
            {
                packno = packet_out->po_packno;
                goto err_never_sent;
            }
            if (packet_out->po_packno == largest_acked(acki))
                imico_take_rtt_sample(conn, packet_out,
                                    packet_in->pi_received, acki->lack_delta);
            TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
            imico_destroy_packet(conn, packet_out);
        }
    }

    if (conn->imc_sent_packnos & ~conn->imc_acked_packnos[pns] & acked)
    {
        LSQ_DEBUG("Newly acked packets, reset handshake count");
        conn->imc_hsk_count = 0;
    }

    conn->imc_acked_packnos[pns] |= acked;

    return parsed_len;

  err_never_sent:
    warn_time = lsquic_time_now();
    if (0 == conn->imc_enpub->enp_last_warning[WT_ACKPARSE_MINI]
        || conn->imc_enpub->enp_last_warning[WT_ACKPARSE_MINI]
                + WARNING_INTERVAL < warn_time)
    {
        conn->imc_enpub->enp_last_warning[WT_ACKPARSE_MINI] = warn_time;
        LSQ_WARN("packet %"PRIu64" (pns: %u) was never sent", packno, pns);
    }
    else
        LSQ_DEBUG("packet %"PRIu64" (pns: %u) was never sent", packno, pns);
    return 0;
}


static unsigned
imico_process_ping_frame (IMICO_PROC_FRAME_ARGS)
{
    LSQ_DEBUG("got a PING frame, do nothing");
    EV_LOG_PING_FRAME_IN(LSQUIC_LOG_CONN_ID);
    return 1;
}


static unsigned
imico_process_connection_close_frame (IMICO_PROC_FRAME_ARGS)
{
    struct lsquic_packet_out *packet_out;
    uint64_t error_code;
    uint16_t reason_len;
    uint8_t reason_off;
    int parsed_len, app_error;

    while ((packet_out = TAILQ_FIRST(&conn->imc_packets_out)))
    {
        TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
        imico_destroy_packet(conn, packet_out);
    }
    conn->imc_flags |= IMC_CLOSE_RECVD;
    parsed_len = conn->imc_conn.cn_pf->pf_parse_connect_close_frame(p, len,
                            &app_error, &error_code, &reason_len, &reason_off);
    if (parsed_len < 0)
    {
        conn->imc_flags |= IMC_PARSE_FAILED;
        return 0;
    }
    EV_LOG_CONNECTION_CLOSE_FRAME_IN(LSQUIC_LOG_CONN_ID, error_code,
                            (int) reason_len, (const char *) p + reason_off);
    LSQ_INFO("Received CONNECTION_CLOSE frame (%s-level code: %"PRIu64"; "
            "reason: %.*s)", app_error ? "application" : "transport",
                error_code, (int) reason_len, (const char *) p + reason_off);
    return 0;   /* This shuts down the connection */
}


static unsigned
imico_process_invalid_frame (IMICO_PROC_FRAME_ARGS)
{
    LSQ_DEBUG("invalid frame %u (%s)", p[0],
        frame_type_2_str[ conn->imc_conn.cn_pf->pf_parse_frame_type(p, len) ]);
    return 0;
}


static unsigned
imico_process_invalid_frame_pv (IMICO_PROC_FRAME_ARGS)
{
    LSQ_DEBUG("invalid frame %u (%s)", p[0],
        frame_type_2_str[ conn->imc_conn.cn_pf->pf_parse_frame_type(p, len) ]);
    ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0, TEC_PROTOCOL_VIOLATION,
                "protocol violation detected while processing frame");
    return 0;
}


static unsigned (*const imico_process_frames[N_QUIC_FRAMES])
                                                (IMICO_PROC_FRAME_ARGS) =
{
    [QUIC_FRAME_PADDING]            =  imico_process_padding_frame,
    [QUIC_FRAME_CRYPTO]             =  imico_process_crypto_frame,
    [QUIC_FRAME_ACK]                =  imico_process_ack_frame,
    [QUIC_FRAME_PING]               =  imico_process_ping_frame,
    [QUIC_FRAME_CONNECTION_CLOSE]   =  imico_process_connection_close_frame,
    /* Some of them are invalid, while others are unexpected.  We treat
     * them the same: handshake cannot proceed.
     */
    [QUIC_FRAME_RST_STREAM]         =  imico_process_invalid_frame,
    [QUIC_FRAME_MAX_DATA]           =  imico_process_invalid_frame,
    [QUIC_FRAME_MAX_STREAM_DATA]    =  imico_process_invalid_frame,
    [QUIC_FRAME_MAX_STREAMS]        =  imico_process_invalid_frame,
    [QUIC_FRAME_BLOCKED]            =  imico_process_invalid_frame,
    [QUIC_FRAME_STREAM_BLOCKED]     =  imico_process_invalid_frame,
    [QUIC_FRAME_STREAMS_BLOCKED]    =  imico_process_invalid_frame,
    [QUIC_FRAME_NEW_CONNECTION_ID]  =  imico_process_invalid_frame_pv,
    [QUIC_FRAME_STOP_SENDING]       =  imico_process_invalid_frame,
    [QUIC_FRAME_PATH_CHALLENGE]     =  imico_process_invalid_frame,
    [QUIC_FRAME_PATH_RESPONSE]      =  imico_process_invalid_frame,
    /* STREAM frame can only come in the App PNS and we delay those packets: */
    [QUIC_FRAME_STREAM]             =  imico_process_invalid_frame,
    [QUIC_FRAME_RETIRE_CONNECTION_ID] =  imico_process_invalid_frame_pv,
    [QUIC_FRAME_NEW_TOKEN]          =  imico_process_invalid_frame_pv,
    [QUIC_FRAME_HANDSHAKE_DONE]     =  imico_process_invalid_frame_pv,
    [QUIC_FRAME_ACK_FREQUENCY]      =  imico_process_invalid_frame,
    [QUIC_FRAME_TIMESTAMP]          =  imico_process_invalid_frame,
};


static unsigned
imico_process_packet_frame (struct ietf_mini_conn *conn,
        struct lsquic_packet_in *packet_in, const unsigned char *p, size_t len)
{
    enum enc_level enc_level;
    enum quic_frame_type type;

    enc_level = lsquic_packet_in_enc_level(packet_in);
    type = conn->imc_conn.cn_pf->pf_parse_frame_type(p, len);
    if (lsquic_legal_frames_by_level[conn->imc_conn.cn_version][enc_level]
                                                                & (1 << type))
    {
        packet_in->pi_frame_types |= 1 << type;
        return imico_process_frames[type](conn, packet_in, p, len);
    }
    else
    {
        LSQ_DEBUG("invalid frame %u at encryption level %s", type,
                                                lsquic_enclev2str[enc_level]);
        if (type == QUIC_FRAME_INVALID)
            ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
                                          TEC_FRAME_ENCODING_ERROR,
                                    "invalid frame %u at encryption level %s",
                                          type, lsquic_enclev2str[enc_level]);
        else
            ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
                                          TEC_PROTOCOL_VIOLATION,
                "protcol violation for invalid frame %u at encryption level %s",
                                          type, lsquic_enclev2str[enc_level]);
        return 0;
    }
}


static int
imico_parse_regular_packet (struct ietf_mini_conn *conn,
                                        struct lsquic_packet_in *packet_in)
{
    const unsigned char *p, *pend;
    unsigned len;

    p = packet_in->pi_data + packet_in->pi_header_sz;
    pend = packet_in->pi_data + packet_in->pi_data_sz;

    if (p >= pend)
    {
        ietf_mini_conn_ci_abort_error(&conn->imc_conn, 0,
                                      TEC_PROTOCOL_VIOLATION,
                                      "packet %"PRIu64" has no frames",
                                      packet_in->pi_packno);
        return -1;
    }
    while (p < pend)
    {
        len = imico_process_packet_frame(conn, packet_in, p, pend - p);
        if (len > 0)
            p += len;
        else
            return -1;
    }

    return 0;
}


static unsigned
highest_bit_set (unsigned long long sz)
{
#if __GNUC__
    unsigned clz = __builtin_clzll(sz);
    return 63 - clz;
#else
    unsigned long y;
    unsigned n;
    n = 64;
    y = sz >> 32;     if (y) { n -= 32; sz = y; }
    y = sz >> 16;     if (y) { n -= 16; sz = y; }
    y = sz >>  8;     if (y) { n -=  8; sz = y; }
    y = sz >>  4;     if (y) { n -=  4; sz = y; }
    y = sz >>  2;     if (y) { n -=  2; sz = y; }
    y = sz >>  1;     if (y) return 63 - n + 2;
    return 63 - n + sz;
#endif
}


static void
ignore_init (struct ietf_mini_conn *conn)
{
    struct lsquic_packet_out *packet_out, *next;
    unsigned count;

    conn->imc_flags |= IMC_IGNORE_INIT;
    conn->imc_flags &= ~(IMC_QUEUED_ACK_INIT << PNS_INIT);

    count = 0;
    for (packet_out = TAILQ_FIRST(&conn->imc_packets_out); packet_out;
                                                            packet_out = next)
    {
        next = TAILQ_NEXT(packet_out, po_next);
        if (PNS_INIT == lsquic_packet_out_pns(packet_out))
        {
            TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
            imico_destroy_packet(conn, packet_out);
            ++count;
        }
    }

    LSQ_DEBUG("henceforth, no Initial packets shall be sent or received; "
        "destroyed %u packet%.*s", count, count != 1, "s");

    int ext_to = conn->imc_enpub->enp_settings.es_idle_timeout * 1000000
            - conn->imc_enpub->enp_settings.es_handshake_to;
    if (ext_to > 0)
    {
        conn->imc_expire += ext_to;
        LSQ_DEBUG("extend mini-conn expire time to %d seconds",
                conn->imc_enpub->enp_settings.es_idle_timeout);
    }
}


static void
imico_maybe_delay_processing (struct ietf_mini_conn *conn,
                                            struct lsquic_packet_in *packet_in)
{
    unsigned max_delayed;

    if (conn->imc_flags & IMC_ADDR_VALIDATED)
        max_delayed = IMICO_MAX_DELAYED_PACKETS_VALIDATED;
    else
        max_delayed = IMICO_MAX_DELAYED_PACKETS_UNVALIDATED;

    if (conn->imc_delayed_packets_count >= max_delayed)
        LSQ_DEBUG("drop packet, already delayed %hhu packets",
                                            conn->imc_delayed_packets_count);
    else if ((packet_in->pi_flags & PI_OWN_DATA) ||
            0 == lsquic_conn_copy_and_release_pi_data(&conn->imc_conn,
                                                conn->imc_enpub, packet_in))
    {
        ++conn->imc_delayed_packets_count;
        lsquic_packet_in_upref(packet_in);
        TAILQ_INSERT_TAIL(&conn->imc_app_packets, packet_in, pi_next);
        LSQ_DEBUG("delay processing of packet (now delayed %hhu)",
            conn->imc_delayed_packets_count);
    }
    else
        LSQ_DEBUG("drop packet, copy data failed");
}


/* [RFC 9000] Section 8.1 (Address Validation During Connection Establishment):
 " Additionally, a server MAY consider the client address validated if
 " the client uses a connection ID chosen by the server and the
 " connection ID contains at least 64 bits of entropy.
 *
 * We use RAND_bytes() to generate SCIDs, so it's all entropy.
 */
static void
imico_maybe_validate_by_dcid (struct ietf_mini_conn *conn,
                                                    const lsquic_cid_t *dcid)
{
    unsigned i;

    if (dcid->len >= 8)
        /* Generic code with unnecessary loop as future-proofing */
        for (i = 0; i < conn->imc_conn.cn_n_cces; ++i)
            if ((conn->imc_conn.cn_cces_mask & (1 << i))
                && (conn->imc_conn.cn_cces[i].cce_flags & CCE_SEQNO)
                && LSQUIC_CIDS_EQ(&conn->imc_conn.cn_cces[i].cce_cid, dcid))
            {
                imico_peer_addr_validated(conn, "dcid/scid + entropy");
                return;
            }
}


static int
imico_received_packet_is_dup (struct ietf_mini_conn *conn,
                                enum packnum_space pns, lsquic_packno_t packno)
{
    if (conn->imc_flags & IMC_TRECHIST)
        return lsquic_trechist_contains(
            conn->imc_recvd_packnos.trechist.hist_masks[pns],
            conn->imc_recvd_packnos.trechist.hist_elems
                                        + TRECHIST_MAX_RANGES * pns, packno);
    else
        return !!(conn->imc_recvd_packnos.bitmasks[pns] & (1ULL << packno));
}


static int
imico_packno_is_largest (struct ietf_mini_conn *conn,
                                enum packnum_space pns, lsquic_packno_t packno)
{
    if (conn->imc_flags & IMC_TRECHIST)
        return 0 == conn->imc_recvd_packnos.trechist.hist_masks[pns]
            || packno > lsquic_trechist_max(
                        conn->imc_recvd_packnos.trechist.hist_masks[pns],
                        conn->imc_recvd_packnos.trechist.hist_elems
                                            + TRECHIST_MAX_RANGES * pns);
    else
        return 0 == conn->imc_recvd_packnos.bitmasks[pns]
            || packno > highest_bit_set(conn->imc_recvd_packnos.bitmasks[pns]);
}


static void
imico_record_recvd_packno (struct ietf_mini_conn *conn,
                                enum packnum_space pns, lsquic_packno_t packno)
{
    if (conn->imc_flags & IMC_TRECHIST)
    {
        if (0 != lsquic_trechist_insert(
                    &conn->imc_recvd_packnos.trechist.hist_masks[pns],
                    conn->imc_recvd_packnos.trechist.hist_elems
                                        + TRECHIST_MAX_RANGES * pns, packno))
        {
            LSQ_INFO("too many ranges for trechist to hold or range too wide");
            conn->imc_flags |= IMC_ERROR;
        }
    }
    else
        conn->imc_recvd_packnos.bitmasks[pns] |= 1ULL << packno;
}


static int
imico_switch_to_trechist (struct ietf_mini_conn *conn)
{
    uint32_t masks[IMICO_N_PNS];
    enum packnum_space pns;
    struct trechist_elem *elems;
    struct ietf_mini_rechist iter;

    elems = malloc(TRECHIST_SIZE * N_PNS);
    if (!elems)
    {
        LSQ_WARN("cannot allocate trechist elems");
        return -1;
    }

    for (pns = 0; pns < IMICO_N_PNS; ++pns)
        if (conn->imc_recvd_packnos.bitmasks[pns])
        {
            lsquic_imico_rechist_init(&iter, conn, pns);
            lsquic_trechist_copy_ranges(&masks[pns],
                                elems + TRECHIST_MAX_RANGES * pns, &iter,
                                lsquic_imico_rechist_first,
                                lsquic_imico_rechist_next);
        }
        else
            masks[pns] = 0;

    memcpy(conn->imc_recvd_packnos.trechist.hist_masks, masks, sizeof(masks));
    conn->imc_recvd_packnos.trechist.hist_elems = elems;
    conn->imc_flags |= IMC_TRECHIST;
    LSQ_DEBUG("switched to trechist");
    return 0;
}


static int
imico_generate_handshake_done (struct ietf_mini_conn *conn);


/* Only a single packet is supported */
static void
ietf_mini_conn_ci_packet_in (struct lsquic_conn *lconn,
                        struct lsquic_packet_in *packet_in)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    enum dec_packin dec_packin;
    enum packnum_space pns;

    /* Update "bytes in" count as early as possible.  From
     * [draft-ietf-quic-transport-28] Section 8.1:
     "                                                 For the purposes of
     " avoiding amplification prior to address validation, servers MUST
     " count all of the payload bytes received in datagrams that are
     " uniquely attributed to a single connection.  This includes datagrams
     " that contain packets that are successfully processed and datagrams
     " that contain packets that are all discarded.
     */
    conn->imc_bytes_in += packet_in->pi_data_sz;
    conn->imc_flags &= ~IMC_AMP_CAPPED;

    if (lconn->cn_enc_session == NULL)
    {
        assert(lconn->cn_flags & LSCONN_PROMOTE_FAIL);
        LSQ_DEBUG("ignore incoming packet: crypto session gone (promotion failed?)");
        return;
    }

    if (conn->imc_flags & IMC_ERROR)
    {
        LSQ_DEBUG("ignore incoming packet: connection is in error state");
        return;
    }

    if (!(conn->imc_flags & (IMC_ADDR_VALIDATED | IMC_PATH_CHANGED)))
        imico_maybe_validate_by_dcid(conn, &packet_in->pi_dcid);

    pns = lsquic_hety2pns[ packet_in->pi_header_type ];
    if (pns == PNS_INIT)
    {
        if (conn->imc_flags & IMC_IGNORE_INIT)
        {
            LSQ_DEBUG("ignore init packet");    /* Don't bother decrypting */
            return;
        }
        if (packet_in->pi_pkt_size
            && packet_in->pi_pkt_size < IQUIC_MIN_INIT_PACKET_SZ)
        {
            LSQ_DEBUG("ignore init packet smaller than minimum size required");
            /* Don't bother decrypting */
            return;
        }
    }

    dec_packin = lconn->cn_esf_c->esf_decrypt_packet(lconn->cn_enc_session,
                                conn->imc_enpub, &conn->imc_conn, packet_in);
    switch (dec_packin)
    {
    case DECPI_OK:
        break;
    case DECPI_VIOLATION:
        ietf_mini_conn_ci_abort_error(lconn, 0, TEC_PROTOCOL_VIOLATION,
                    "protocol violation detected while decrypting packet");
        return;
    case DECPI_NOT_YET:
        imico_maybe_delay_processing(conn, packet_in);
        return;
    case DECPI_BADCRYPT:
        if (packet_in->pi_flags & PI_FIRST_INIT)
        {
            LSQ_DEBUG("possible packet corruption, destroy mini-conn.");
            conn->imc_flags |= IMC_ERROR | IMC_CLOSE_RECVD;
            /* avoid adding CID to black list */
            conn->imc_conn.cn_flags |= LSCONN_NO_BL;
        }
        //fall through
    default:
        LSQ_DEBUG("could not decrypt packet");
        return;
    }

    EV_LOG_PACKET_IN(LSQUIC_LOG_CONN_ID, packet_in);

    if (pns == PNS_APP)
    {
        imico_maybe_delay_processing(conn, packet_in);
        return;
    }
    else if (pns == PNS_HSK)
    {
        if (!LSQUIC_CIDS_EQ(CN_SCID(&conn->imc_conn), &packet_in->pi_dcid))
        {
            ietf_mini_conn_ci_abort_error(lconn, 0, TEC_PROTOCOL_VIOLATION,
                    "protocol violation detected bad dcid for HSK pns");
            return;
        }
        if (!(conn->imc_flags & (IMC_ADDR_VALIDATED | IMC_PATH_CHANGED)))
            imico_peer_addr_validated(conn, "handshake PNS");
    }

    if (((conn->imc_flags >> IMCBIT_PNS_BIT_SHIFT) & 3) < pns)
    {
        conn->imc_flags &= ~(3 << IMCBIT_PNS_BIT_SHIFT);
        conn->imc_flags |= pns << IMCBIT_PNS_BIT_SHIFT;
    }

    if (pns == PNS_HSK && !(conn->imc_flags & IMC_IGNORE_INIT))
        ignore_init(conn);

    if (packet_in->pi_packno > MAX_PACKETS
                                    && !(conn->imc_flags & IMC_TRECHIST))
    {
        if (0 != imico_switch_to_trechist(conn))
            return;
    }

    if (imico_received_packet_is_dup(conn, pns, packet_in->pi_packno))
    {
        LSQ_DEBUG("duplicate packet %"PRIu64, packet_in->pi_packno);
        return;
    }

    /* Update receive history before processing the packet: if there is an
     * error, the connection is terminated and recording this packet number
     * is helpful when it is printed along with other diagnostics in dtor.
     */
    if (imico_packno_is_largest(conn, pns, packet_in->pi_packno))
        conn->imc_largest_recvd[pns] = packet_in->pi_received;
    imico_record_recvd_packno(conn, pns, packet_in->pi_packno);

    if (!(conn->imc_flags & (IMC_QUEUED_ACK_INIT << pns)))
    {
        LSQ_DEBUG("queued ACK in %s", lsquic_pns2str[pns]);
        conn->imc_flags |= IMC_QUEUED_ACK_INIT << pns;
    }
    ++conn->imc_ecn_counts_in[pns][ lsquic_packet_in_ecn(packet_in) ];
    conn->imc_incoming_ecn <<= 1;
    conn->imc_incoming_ecn |= lsquic_packet_in_ecn(packet_in) != ECN_NOT_ECT;

    if (0 != imico_parse_regular_packet(conn, packet_in))
    {
        LSQ_DEBUG("connection is now in error state");
        conn->imc_flags |= IMC_ERROR;
        return;
    }
    if (conn->imc_flags & IMC_HSK_OK)
    {
        if (lconn->cn_esf.i->esfi_in_init(lconn->cn_enc_session))
            LSQ_DEBUG("still in init, defer HANDSHAKE_DONE");
        else if (0 != imico_generate_handshake_done(conn))
            conn->imc_flags |= IMC_ERROR;
    }
}


static void
ietf_mini_conn_ci_packet_sent (struct lsquic_conn *lconn,
                              struct lsquic_packet_out *packet_out)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    conn->imc_sent_packnos |= 1ULL << packet_out->po_packno;
    conn->imc_ecn_packnos |= !!lsquic_packet_out_ecn(packet_out)
                                                    << packet_out->po_packno;
#if 0
    if (packet_out->po_frame_types & (1 << QUIC_FRAME_ACK))
    {
        assert(mc->mc_flags & MC_UNSENT_ACK);
        mc->mc_flags &= ~MC_UNSENT_ACK;
    }
#endif
    if (packet_out->po_header_type == HETY_HANDSHAKE)
        conn->imc_flags |= IMC_HSK_PACKET_SENT;
    LSQ_DEBUG("%s: packet %"PRIu64" sent", __func__, packet_out->po_packno);
}


static void
ietf_mini_conn_ci_packet_not_sent (struct lsquic_conn *lconn,
                              struct lsquic_packet_out *packet_out)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    size_t packet_size;

    packet_out->po_flags &= ~PO_SENT;
    packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
    conn->imc_bytes_out -= packet_size;
    LSQ_DEBUG("%s: packet %"PRIu64" not sent", __func__, packet_out->po_packno);
}


static inline void
imico_return_enc_data (struct ietf_mini_conn *conn,
                                        struct lsquic_packet_out *packet_out)
{
    conn->imc_enpub->enp_pmi->pmi_return(conn->imc_enpub->enp_pmi_ctx,
        conn->imc_path.np_peer_ctx, packet_out->po_enc_data,
        lsquic_packet_out_ipv6(packet_out));
    packet_out->po_flags &= ~PO_ENCRYPTED;
    packet_out->po_enc_data = NULL;
}


static inline int
imico_repackage_packet (struct ietf_mini_conn *conn,
                                        struct lsquic_packet_out *packet_out)
{
    const lsquic_packno_t oldno = packet_out->po_packno;
    const lsquic_packno_t packno = conn->imc_next_packno++;
    if (packno > MAX_PACKETS)
        return -1;

    EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "schedule resend, repackage packet "
                      "#%"PRIu64" -> #%"PRIu64, oldno, packno);
    packet_out->po_packno = packno;
    packet_out->po_flags &= ~PO_SENT;
    ++packet_out->po_retx_cnt;
    lsquic_packet_out_set_ecn(packet_out, imico_get_ecn(conn));
    if (packet_out->po_flags & PO_ENCRYPTED)
        imico_return_enc_data(conn, packet_out);
    TAILQ_INSERT_TAIL(&conn->imc_packets_out, packet_out, po_next);
    return 0;
}


static int
imico_handle_losses_and_have_unsent (struct ietf_mini_conn *conn,
                                                            lsquic_time_t now)
{
    TAILQ_HEAD(, lsquic_packet_out) lost_packets =
                                    TAILQ_HEAD_INITIALIZER(lost_packets);
    const struct lsquic_conn *const lconn = &conn->imc_conn;
    lsquic_packet_out_t *packet_out, *next;
    lsquic_time_t retx_to = 0;
    unsigned n_to_send = 0;
    size_t packet_size;

    for (packet_out = TAILQ_FIRST(&conn->imc_packets_out); packet_out;
                                                        packet_out = next)
    {
        next = TAILQ_NEXT(packet_out, po_next);
        if (packet_out->po_flags & PO_SENT)
        {
            if (packet_out->po_frame_types & IQUIC_FRAME_RETX_MASK)
            {
                if (0 == retx_to)
                    retx_to = imico_calc_retx_timeout(conn);
                if (conn->imc_hsk_count == 0)
                    packet_out->po_retx_cnt = 0;
                if (packet_out->po_sent
                    + (retx_to << (packet_out->po_retx_cnt >> 1)) < now)
                {
                    LSQ_DEBUG("packet %"PRIu64" has been lost (rto: %"PRIu64")",
                              packet_out->po_packno,
                              retx_to << (packet_out->po_retx_cnt >> 1));
                    TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
                    TAILQ_INSERT_TAIL(&lost_packets, packet_out, po_next);
                }
            }
            else
            {
                TAILQ_REMOVE(&conn->imc_packets_out, packet_out, po_next);
                imico_destroy_packet(conn, packet_out);
            }
        }
        else if (packet_size = lsquic_packet_out_total_sz(lconn, packet_out),
                                                imico_can_send(conn, packet_size))
            ++n_to_send;
        else
            break;
    }

    conn->imc_hsk_count += !TAILQ_EMPTY(&lost_packets);

    while ((packet_out = TAILQ_FIRST(&lost_packets)))
    {
        TAILQ_REMOVE(&lost_packets, packet_out, po_next);
        if (0 == imico_repackage_packet(conn, packet_out))
        {
            packet_size = lsquic_packet_out_total_sz(lconn, packet_out);
            if (imico_can_send(conn, packet_size))
                ++n_to_send;
        }
        else
            imico_destroy_packet(conn, packet_out);
    }

    return n_to_send > 0;
}


static inline int
imico_have_packets_to_send (struct ietf_mini_conn *conn, lsquic_time_t now)
{
    return imico_handle_losses_and_have_unsent(conn, now);
}


void
lsquic_imico_rechist_init (struct ietf_mini_rechist *rechist,
                    const struct ietf_mini_conn *conn, enum packnum_space pns)
{
    assert(pns < IMICO_N_PNS);
    rechist->conn = conn;
    rechist->pns  = pns;
    if (conn->imc_flags & IMC_TRECHIST)
        lsquic_trechist_iter(&rechist->u.trechist_iter,
            conn->imc_recvd_packnos.trechist.hist_masks[pns],
            conn->imc_recvd_packnos.trechist.hist_elems + TRECHIST_MAX_RANGES * pns);
    else
    {
        rechist->u.bitmask.cur_set = 0;
        rechist->u.bitmask.cur_idx = 0;
    }
}


static inline lsquic_time_t
imico_rechist_largest_recv (void *rechist_ctx)
{
    struct ietf_mini_rechist *rechist = rechist_ctx;
    return rechist->conn->imc_largest_recvd[ rechist->pns ];
}


static const struct lsquic_packno_range *
imico_bitmask_rechist_next (struct ietf_mini_rechist *rechist)
{
    const struct ietf_mini_conn *conn = rechist->conn;
    packno_set_t packnos;
    int i;

    packnos = rechist->u.bitmask.cur_set;
    if (0 == packnos)
        return NULL;

    /* There may be a faster way to do this, but for now, we just want
     * correctness.
     */
    for (i = rechist->u.bitmask.cur_idx; i >= 0; --i)
        if (packnos & (1ULL << i))
        {
            rechist->u.bitmask.range.low  = i;
            rechist->u.bitmask.range.high = i;
            break;
        }
    assert(i >= 0); /* We must have hit at least one bit */
    --i;
    for ( ; i >= 0 && (packnos & (1ULL << i)); --i)
        rechist->u.bitmask.range.low = i;
    if (i >= 0)
    {
        rechist->u.bitmask.cur_set = packnos & ((1ULL << i) - 1);
        rechist->u.bitmask.cur_idx = i;
    }
    else
        rechist->u.bitmask.cur_set = 0;
    LSQ_DEBUG("%s: return [%"PRIu64", %"PRIu64"]", __func__,
                rechist->u.bitmask.range.low, rechist->u.bitmask.range.high);
    return &rechist->u.bitmask.range;
}


const struct lsquic_packno_range *
lsquic_imico_rechist_next (void *rechist_ctx)
{
    struct ietf_mini_rechist *rechist = rechist_ctx;

    if (rechist->conn->imc_flags & IMC_TRECHIST)
        return lsquic_trechist_next(&rechist->u.trechist_iter);
    else
        return imico_bitmask_rechist_next(rechist);
}


const struct lsquic_packno_range *
lsquic_imico_rechist_first (void *rechist_ctx)
{
    struct ietf_mini_rechist *rechist = rechist_ctx;

    if (rechist->conn->imc_flags & IMC_TRECHIST)
        return lsquic_trechist_first(&rechist->u.trechist_iter);
    else
    {
        rechist->u.bitmask.cur_set
                = rechist->conn->imc_recvd_packnos.bitmasks[ rechist->pns ];
        rechist->u.bitmask.cur_idx
                = highest_bit_set(rechist->u.bitmask.cur_set);
        return lsquic_imico_rechist_next(rechist_ctx);
    }
}


static const enum header_type pns2hety[] =
{
    [PNS_INIT]  = HETY_INITIAL,
    [PNS_HSK]   = HETY_HANDSHAKE,
    [PNS_APP]   = HETY_SHORT,
};


static inline int
imico_generate_ping (struct ietf_mini_conn *conn,
                     struct lsquic_packet_out *packet_out)
{
    int len;
    len = conn->imc_conn.cn_pf->pf_gen_ping_frame(
                packet_out->po_data + packet_out->po_data_sz,
                lsquic_packet_out_avail(packet_out));
    if (len > 0)
    {
        packet_out->po_frame_types |= 1 << QUIC_FRAME_PING;
        packet_out->po_data_sz += len;
        packet_out->po_regen_sz += len;
        LSQ_DEBUG("wrote PING frame of size %d to packet %" PRIu64,
                len,  packet_out->po_packno);
    }
    return 0;
}


static int
imico_generate_ack (struct ietf_mini_conn *conn, enum packnum_space pns,
                                                            lsquic_time_t now)
{
    struct lsquic_packet_out *packet_out;
    enum header_type header_type;
    int len;

    header_type = pns2hety[pns];
    packet_out = imico_get_packet_out(conn, header_type, 0);
    if (!packet_out)
        return -1;

    len = imico_build_ack_frame(conn, pns, now,
            packet_out->po_data + packet_out->po_data_sz,
            lsquic_packet_out_avail(packet_out),
            &packet_out->po_ack2ed);
    if (len < 0)
        return -1;

    packet_out->po_frame_types |= 1 << QUIC_FRAME_ACK;
    packet_out->po_data_sz += len;
    packet_out->po_regen_sz += len;
    conn->imc_flags &= ~(IMC_QUEUED_ACK_INIT << pns);
    LSQ_DEBUG("wrote ACK frame of size %d in %s to packet %" PRIu64,
              len, lsquic_pns2str[pns], packet_out->po_packno);
    if (pns == PNS_INIT && conn->imc_hsk_count > 0)
        imico_generate_ping(conn, packet_out);
    return 0;
}


static int
imico_generate_acks (struct ietf_mini_conn *conn, lsquic_time_t now)
{
    enum packnum_space pns;

    for (pns = PNS_INIT; pns < IMICO_N_PNS; ++pns)
        if (conn->imc_flags & (IMC_QUEUED_ACK_INIT << pns)
                && !(pns == PNS_INIT && (conn->imc_flags & IMC_IGNORE_INIT)))
        {
            if (0 != imico_generate_ack(conn, pns, now))
                return -1;
        }
    return 0;
}


int
lsquic_mini_conn_ietf_pre_promote(struct ietf_mini_conn *conn,
                                  lsquic_time_t now)
{
    if (conn->imc_flags & (IMC_QUEUED_ACK_INIT|IMC_QUEUED_ACK_HSK))
    {
        if (0 != imico_generate_acks(conn, now))
        {
            conn->imc_flags |= IMC_ERROR;
            return -1;
        }
    }
    return 0;
}


static void
imico_generate_conn_close (struct ietf_mini_conn *conn)
{
    struct lsquic_packet_out *packet_out;
    enum header_type header_type;
    enum packnum_space pns, pns_max;
    unsigned error_code;
    const char *reason;
    size_t need;
    int sz, rlen, is_app;
    char reason_buf[0x20];

    if (conn->imc_flags & IMC_ABORT_ERROR)
    {
        is_app = !!(conn->imc_flags & IMC_ABORT_ISAPP);
        error_code = conn->imc_error_code;
        reason = NULL;
        rlen = 0;
    }
    else if (conn->imc_flags & IMC_TLS_ALERT)
    {
        is_app = 0;
        error_code = 0x100 + conn->imc_tls_alert;
        if (ALERT_NO_APPLICATION_PROTOCOL == conn->imc_tls_alert)
            reason = "no suitable application protocol";
        else
        {
            snprintf(reason_buf, sizeof(reason_buf), "TLS alert %"PRIu8,
                                                        conn->imc_tls_alert);
            reason = reason_buf;
        }
        rlen = strlen(reason);
    }
    else if (conn->imc_flags & IMC_BAD_TRANS_PARAMS)
    {
        is_app = 0;
        error_code = TEC_TRANSPORT_PARAMETER_ERROR;
        reason = "bad transport parameters";
        rlen = 24;
    }
    else if (conn->imc_flags & IMC_VER_NEG_FAILED)
    {
        is_app = 0;
        error_code = TEC_VERSION_NEGOTIATION_ERROR;
        reason = "version negociation failed";
        rlen = 26;
    }
    else if (conn->imc_flags & IMC_HSK_FAILED)
    {
        is_app = 0;
        error_code = TEC_NO_ERROR;
        reason = "handshake failed";
        rlen = 16;
    }
    else if (conn->imc_flags & IMC_PARSE_FAILED)
    {
        is_app = 0;
        error_code = TEC_FRAME_ENCODING_ERROR;
        reason = "cannot decode frame";
        rlen = 19;
    }
    else
    {
        is_app = 0;
        error_code = TEC_INTERNAL_ERROR;
        reason = NULL;
        rlen = 0;
    }


/* [RFC 9000] Section 10.2.3 (Immediate Close during the Handshake):
 *
 " A client will always know whether the server has Handshake keys (see
 " Section 17.2.2.1), but it is possible that a server does not know
 " whether the client has Handshake keys.  Under these circumstances, a
 " server SHOULD send a CONNECTION_CLOSE frame in both Handshake and
 " Initial packets to ensure that at least one of them is processable by
 " the client.
--- 8< ---
 " Sending a CONNECTION_CLOSE of type 0x1d in an Initial or Handshake
 " packet could expose application state or be used to alter application
 " state.  A CONNECTION_CLOSE of type 0x1d MUST be replaced by a
 " CONNECTION_CLOSE of type 0x1c when sending the frame in Initial or
 " Handshake packets.  Otherwise, information about the application
 " state might be revealed.  Endpoints MUST clear the value of the
 " Reason Phrase field and SHOULD use the APPLICATION_ERROR code when
 " converting to a CONNECTION_CLOSE of type 0x1c.
 */
    LSQ_DEBUG("sending CONNECTION_CLOSE, is_app: %d, error code: %u, "
        "reason: %.*s", is_app, error_code, rlen, reason);
    if (is_app && conn->imc_conn.cn_version > LSQVER_ID27)
    {
        LSQ_DEBUG("convert to 0x1C, replace code and reason");
        is_app = 0;
        error_code = TEC_APPLICATION_ERROR;
        rlen = 0;
    }

    pns = (conn->imc_flags >> IMCBIT_PNS_BIT_SHIFT) & 3;
    switch ((!!(conn->imc_flags & IMC_HSK_PACKET_SENT) << 1)
                | (pns == PNS_HSK) /* Handshake packet received */)
    {
    case (0 << 1) | 0:
        pns = PNS_INIT;
        pns_max = PNS_INIT;
        break;
    case (1 << 1) | 0:
        pns = PNS_INIT;
        pns_max = PNS_HSK;
        break;
    default:
        pns = PNS_HSK;
        pns_max = PNS_HSK;
        break;
    }

    need = conn->imc_conn.cn_pf->pf_connect_close_frame_size(is_app,
                                                        error_code, 0, rlen);
    LSQ_DEBUG("will generate %u CONNECTION_CLOSE frame%.*s",
        pns_max - pns + 1, pns_max > pns, "s");
    do
    {
        header_type = pns2hety[pns];
        packet_out = imico_get_packet_out(conn, header_type, need);
        if (!packet_out)
            return;
        sz = conn->imc_conn.cn_pf->pf_gen_connect_close_frame(
                 packet_out->po_data + packet_out->po_data_sz,
                 lsquic_packet_out_avail(packet_out), is_app, error_code, reason,
                 rlen);
        if (sz >= 0)
        {
            packet_out->po_frame_types |= 1 << QUIC_FRAME_CONNECTION_CLOSE;
            packet_out->po_data_sz += sz;
            LSQ_DEBUG("generated CONNECTION_CLOSE frame");
        }
        else
            LSQ_WARN("could not generate CONNECTION_CLOSE frame");
        ++pns;
    }
    while (pns <= pns_max);
}


static int
imico_generate_handshake_done (struct ietf_mini_conn *conn)
{
    struct lsquic_packet_out *packet_out;
    unsigned need;
    int sz;

    need = conn->imc_conn.cn_pf->pf_handshake_done_frame_size();
    packet_out = imico_get_packet_out(conn, HETY_SHORT, need);
    if (!packet_out)
        return -1;
    sz = conn->imc_conn.cn_pf->pf_gen_handshake_done_frame(
                 packet_out->po_data + packet_out->po_data_sz,
                 lsquic_packet_out_avail(packet_out));
    if (sz < 0)
    {
        LSQ_WARN("could not generate HANDSHAKE_DONE frame");
        return -1;
    }

    packet_out->po_frame_types |= 1 << QUIC_FRAME_HANDSHAKE_DONE;
    packet_out->po_data_sz += sz;
    LSQ_DEBUG("generated HANDSHAKE_DONE frame");
    conn->imc_flags |= IMC_HSK_DONE_SENT;

    return 0;
}


static enum tick_st
ietf_mini_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    enum tick_st tick;

    /* Shortcut promotion: all ticks are now off */
    if (lconn->cn_flags & (LSCONN_PROMOTED|LSCONN_PROMOTE_FAIL))
        return TICK_CLOSE;

    if (conn->imc_expire < now)
    {
        LSQ_DEBUG("connection expired: closing");
        return TICK_CLOSE;
    }

    if (!(conn->imc_flags &
                    (IMC_HAVE_TP|IMC_ADDR_VALIDATED|IMC_BAD_TRANS_PARAMS))
                            && conn->imc_enpub->enp_settings.es_support_srej)
    {
        if (conn->imc_flags & IMC_ERROR)
            goto close_on_error;
        LSQ_DEBUG("Peer not validated and do not have transport parameters "
            "on the first tick: retry");
        return TICK_RETRY|TICK_CLOSE;
    }

    if (conn->imc_flags & (IMC_QUEUED_ACK_INIT|IMC_QUEUED_ACK_HSK))
    {
        if (0 != imico_generate_acks(conn, now))
        {
            conn->imc_flags |= IMC_ERROR;
            return TICK_CLOSE;
        }
    }


    tick = 0;

    if (conn->imc_flags & IMC_ERROR)
    {
  close_on_error:
        if (!(conn->imc_flags & IMC_CLOSE_RECVD))
            imico_generate_conn_close(conn);
        tick = TICK_CLOSE;
    }
    else if (conn->imc_flags & IMC_HSK_OK)
    {
        if (lconn->cn_esf.i->esfi_in_init(lconn->cn_enc_session))
            LSQ_DEBUG("still in init, defer HANDSHAKE_DONE");
        else if (0 != imico_generate_handshake_done(conn))
            goto close_on_error;
        tick |= TICK_PROMOTE;
    }

    if (imico_have_packets_to_send(conn, now))
        tick |= TICK_SEND;
    else
        tick |= TICK_QUIET;

    LSQ_DEBUG("Return TICK %d", tick);
    return tick;
}


static void
ietf_mini_conn_ci_internal_error (struct lsquic_conn *lconn,
                                                    const char *format, ...)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    LSQ_INFO("internal error reported");
    conn->imc_flags |= IMC_ERROR;
}


static void
ietf_mini_conn_ci_abort_error (struct lsquic_conn *lconn, int is_app,
                                unsigned error_code, const char *fmt, ...)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    va_list ap;
    const char *err_str, *percent;
    char err_buf[0x100];

    percent = strchr(fmt, '%');
    if (percent)
    {
        va_start(ap, fmt);
        vsnprintf(err_buf, sizeof(err_buf), fmt, ap);
        va_end(ap);
        err_str = err_buf;
    }
    else
        err_str = fmt;
    LSQ_INFO("abort error: is_app: %d; error code: %u; error str: %s",
        is_app, error_code, err_str);
    conn->imc_flags |= IMC_ERROR|IMC_ABORT_ERROR;
    if (is_app)
        conn->imc_flags |= IMC_ABORT_ISAPP;
    conn->imc_error_code = error_code;
}


static struct network_path *
ietf_mini_conn_ci_get_path (struct lsquic_conn *lconn,
                                                    const struct sockaddr *sa)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;

    return &conn->imc_path;
}


static unsigned char
ietf_mini_conn_ci_record_addrs (struct lsquic_conn *lconn, void *peer_ctx,
            const struct sockaddr *local_sa, const struct sockaddr *peer_sa)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;
    const struct sockaddr *orig_peer_sa;
    struct lsquic_packet_out *packet_out;
    size_t len;
    char path_str[4][INET6_ADDRSTRLEN + sizeof(":65535")];

    if (NP_IS_IPv6(&conn->imc_path) != (AF_INET6 == peer_sa->sa_family))
        TAILQ_FOREACH(packet_out, &conn->imc_packets_out, po_next)
            if ((packet_out->po_flags & (PO_SENT|PO_ENCRYPTED)) == PO_ENCRYPTED)
                imico_return_enc_data(conn, packet_out);

    orig_peer_sa = NP_PEER_SA(&conn->imc_path);
    if (orig_peer_sa->sa_family == 0)
        LSQ_DEBUG("connection to %s from %s", SA2STR(local_sa, path_str[0]),
                                                SA2STR(peer_sa, path_str[1]));
    else if (!(lsquic_sockaddr_eq(NP_PEER_SA(&conn->imc_path), peer_sa)
              && lsquic_sockaddr_eq(NP_LOCAL_SA(&conn->imc_path), local_sa)))
    {
        LSQ_DEBUG("path changed from (%s - %s) to (%s - %s)",
            SA2STR(NP_LOCAL_SA(&conn->imc_path), path_str[0]),
            SA2STR(NP_PEER_SA(&conn->imc_path), path_str[1]),
            SA2STR(local_sa, path_str[2]),
            SA2STR(peer_sa, path_str[3]));
        conn->imc_flags = (conn->imc_flags & ~IMC_ADDR_VALIDATED)
                            | IMC_PATH_CHANGED;
    }

    len = local_sa->sa_family == AF_INET ? sizeof(struct sockaddr_in)
                                                : sizeof(struct sockaddr_in6);

    memcpy(conn->imc_path.np_peer_addr, peer_sa, len);
    memcpy(conn->imc_path.np_local_addr, local_sa, len);
    conn->imc_path.np_peer_ctx = peer_ctx;
    return 0;
}


void
ietf_mini_conn_ci_count_garbage (struct lsquic_conn *lconn, size_t garbage_sz)
{
    struct ietf_mini_conn *conn = (struct ietf_mini_conn *) lconn;

    conn->imc_bytes_in += garbage_sz;
    conn->imc_flags &= ~IMC_AMP_CAPPED;
    LSQ_DEBUG("count %zd bytes of garbage, new value: %u bytes", garbage_sz,
        conn->imc_bytes_in);
}


static const struct conn_iface mini_conn_ietf_iface = {
    .ci_abort_error          =  ietf_mini_conn_ci_abort_error,
    .ci_client_call_on_new   =  ietf_mini_conn_ci_client_call_on_new,
    .ci_count_garbage        =  ietf_mini_conn_ci_count_garbage,
    .ci_destroy              =  ietf_mini_conn_ci_destroy,
    .ci_get_engine           =  ietf_mini_conn_ci_get_engine,
    .ci_get_path             =  ietf_mini_conn_ci_get_path,
    .ci_hsk_done             =  ietf_mini_conn_ci_hsk_done,
    .ci_internal_error       =  ietf_mini_conn_ci_internal_error,
    .ci_is_tickable          =  ietf_mini_conn_ci_is_tickable,
    .ci_next_packet_to_send  =  ietf_mini_conn_ci_next_packet_to_send,
    .ci_next_tick_time       =  ietf_mini_conn_ci_next_tick_time,
    .ci_packet_in            =  ietf_mini_conn_ci_packet_in,
    .ci_packet_not_sent      =  ietf_mini_conn_ci_packet_not_sent,
    .ci_packet_sent          =  ietf_mini_conn_ci_packet_sent,
    .ci_record_addrs         =  ietf_mini_conn_ci_record_addrs,
    .ci_tick                 =  ietf_mini_conn_ci_tick,
    .ci_tls_alert            =  ietf_mini_conn_ci_tls_alert,
};
