
/*
* Author: Christian Huitema
* Copyright (c) 2017, Private Octopus, Inc.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "picoquic.h"
#include "picoquic_internal.h"
#include "picoquic_unified_log.h"
#include "tls_api.h"
#include <stdlib.h>
#include <string.h>
#ifndef _WINDOWS
#include <sys/time.h>
#endif


/*
 * Supported versions. Specific versions may mandate different processing of different
 * formats.
 * The first version in the list is the preferred version.
 * The protection of clear text packets will be a function of the version negotiation.
 */

static uint8_t picoquic_cleartext_internal_test_1_salt[] = {
    0x30, 0x67, 0x16, 0xd7, 0x63, 0x75, 0xd5, 0x55,
    0x4b, 0x2f, 0x60, 0x5e, 0xef, 0x78, 0xd8, 0x33,
    0x3d, 0xc1, 0xca, 0x36
};

static uint8_t picoquic_cleartext_draft_23_salt[] = {
    0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a,
    0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65,
    0xbe, 0xf9, 0xf5, 0x02
};

uint8_t picoquic_retry_protection_key_25[32] = {
    0x65, 0x6e, 0x61, 0xe3, 0x36, 0xae, 0x94, 0x17, 0xf7, 0xf0, 0xed, 0xd8, 0xd7, 0x8d, 0x46, 0x1e,
    0x2a, 0xa7, 0x08, 0x4a, 0xba, 0x7a, 0x14, 0xc1, 0xe9, 0xf7, 0x26, 0xd5, 0x57, 0x09, 0x16, 0x9a };

static uint8_t picoquic_cleartext_draft_29_salt[] = {
    0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c,
    0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0,
    0x43, 0x90, 0xa8, 0x99
};

uint8_t picoquic_retry_protection_key_29[32] = {
    0x8b, 0x0d, 0x37, 0xeb, 0x85, 0x35, 0x02, 0x2e, 0xbc, 0x8d, 0x76, 0xa2, 0x07, 0xd8, 0x0d, 0xf2,
    0x26, 0x46, 0xec, 0x06, 0xdc, 0x80, 0x96, 0x42, 0xc3, 0x0a, 0x8b, 0xaa, 0x2b, 0xaa, 0xff, 0x4c };

const picoquic_version_parameters_t picoquic_supported_versions[] = {
    { PICOQUIC_TWENTIETH_INTEROP_VERSION,
        sizeof(picoquic_cleartext_draft_29_salt),
        picoquic_cleartext_draft_29_salt,
        sizeof(picoquic_retry_protection_key_29),
        picoquic_retry_protection_key_29 },
    { PICOQUIC_TWENTIETH_PRE_INTEROP_VERSION,
        sizeof(picoquic_cleartext_draft_29_salt),
        picoquic_cleartext_draft_29_salt,
        sizeof(picoquic_retry_protection_key_29),
        picoquic_retry_protection_key_29 },
    { PICOQUIC_NINETEENTH_INTEROP_VERSION,
        sizeof(picoquic_cleartext_draft_29_salt),
        picoquic_cleartext_draft_29_salt,
        sizeof(picoquic_retry_protection_key_29),
        picoquic_retry_protection_key_29 },
    { PICOQUIC_NINETEENTH_BIS_INTEROP_VERSION,
        sizeof(picoquic_cleartext_draft_29_salt),
        picoquic_cleartext_draft_29_salt,
        sizeof(picoquic_retry_protection_key_29),
        picoquic_retry_protection_key_29 },
    { PICOQUIC_EIGHTEENTH_INTEROP_VERSION,
        sizeof(picoquic_cleartext_draft_23_salt),
        picoquic_cleartext_draft_23_salt,
        sizeof(picoquic_retry_protection_key_25),
        picoquic_retry_protection_key_25 },
    { PICOQUIC_SEVENTEENTH_INTEROP_VERSION,
        sizeof(picoquic_cleartext_draft_23_salt),
        picoquic_cleartext_draft_23_salt,
        sizeof(picoquic_retry_protection_key_25),
        picoquic_retry_protection_key_25 },
    { PICOQUIC_INTERNAL_TEST_VERSION_2,
        sizeof(picoquic_cleartext_internal_test_1_salt),
        picoquic_cleartext_internal_test_1_salt,
        sizeof(picoquic_retry_protection_key_25),
        picoquic_retry_protection_key_25},
    { PICOQUIC_INTERNAL_TEST_VERSION_1,
        sizeof(picoquic_cleartext_internal_test_1_salt),
        picoquic_cleartext_internal_test_1_salt,
        sizeof(picoquic_retry_protection_key_25),
        picoquic_retry_protection_key_25 }
};

const size_t picoquic_nb_supported_versions = sizeof(picoquic_supported_versions) / sizeof(picoquic_version_parameters_t);

/*
* Structures used in the hash table of connections
*/
typedef struct st_picoquic_cnx_id_key_t {
    picoquic_connection_id_t cnx_id;
    picoquic_cnx_t* cnx;
    picoquic_local_cnxid_t* l_cid;
    struct st_picoquic_cnx_id_key_t* next_cnx_id;
} picoquic_cnx_id_key_t;

typedef struct st_picoquic_net_id_key_t {
    struct sockaddr_storage saddr;
    picoquic_cnx_t* cnx;
    picoquic_path_t* path;
    struct st_picoquic_net_id_key_t* next_net_id;
} picoquic_net_id_key_t;

typedef struct st_picoquic_net_icid_key_t {
    struct sockaddr_storage saddr;
    picoquic_connection_id_t icid;
    picoquic_cnx_t* cnx;
} picoquic_net_icid_key_t;

typedef struct st_picoquic_net_secret_key_t {
    struct sockaddr_storage saddr;
    uint8_t reset_secret[PICOQUIC_RESET_SECRET_SIZE];
    picoquic_cnx_t* cnx;
} picoquic_net_secret_key_t;

/* Hash and compare for CNX hash tables */
static uint64_t picoquic_cnx_id_hash(const void* key)
{
    const picoquic_cnx_id_key_t* cid = (const picoquic_cnx_id_key_t*)key;
    return picoquic_connection_id_hash(&cid->cnx_id);
}

static int picoquic_cnx_id_compare(const void* key1, const void* key2)
{
    const picoquic_cnx_id_key_t* cid1 = (const picoquic_cnx_id_key_t*)key1;
    const picoquic_cnx_id_key_t* cid2 = (const picoquic_cnx_id_key_t*)key2;

    return picoquic_compare_connection_id(&cid1->cnx_id, &cid2->cnx_id);
}

static uint64_t picoquic_net_id_hash(const void* key)
{
    const picoquic_net_id_key_t* net = (const picoquic_net_id_key_t*)key;

    // return picohash_bytes((uint8_t*)&net->saddr, sizeof(net->saddr));
    return picoquic_hash_addr((struct sockaddr*) & net->saddr);
}

static int picoquic_net_id_compare(const void* key1, const void* key2)
{
    const picoquic_net_id_key_t* net1 = (const picoquic_net_id_key_t*)key1;
    const picoquic_net_id_key_t* net2 = (const picoquic_net_id_key_t*)key2;

    return picoquic_compare_addr((struct sockaddr*) & net1->saddr, (struct sockaddr*) & net2->saddr);
}

static uint64_t picoquic_net_icid_hash(const void* key)
{
    const picoquic_net_icid_key_t* net_icid = (const picoquic_net_icid_key_t*)key;

    return picohash_hash_mix(picoquic_hash_addr((struct sockaddr*) & net_icid->saddr), 
        picoquic_connection_id_hash(&net_icid->icid));

}

static int picoquic_net_icid_compare(const void* key1, const void* key2)
{
    const picoquic_net_icid_key_t* net_icid1 = (const picoquic_net_icid_key_t*)key1;
    const picoquic_net_icid_key_t* net_icid2 = (const picoquic_net_icid_key_t*)key2;
    int ret = picoquic_compare_addr((struct sockaddr*) & net_icid1->saddr, (struct sockaddr*) & net_icid2->saddr);
    if (ret == 0) {
        ret = picoquic_compare_connection_id(&net_icid1->icid, &net_icid2->icid);
    }

    return ret;
}

static uint64_t picoquic_net_secret_hash(const void* key)
{
    const picoquic_net_secret_key_t* net_secret = (const picoquic_net_secret_key_t*)key;

    return picohash_hash_mix(picoquic_hash_addr((struct sockaddr*) & net_secret->saddr),
        picohash_bytes(net_secret->reset_secret, PICOQUIC_RESET_SECRET_SIZE));

}

static int picoquic_net_secret_compare(const void* key1, const void* key2)
{
    const picoquic_net_secret_key_t* net_secret1 = (const picoquic_net_secret_key_t*)key1;
    const picoquic_net_secret_key_t* net_secret2 = (const picoquic_net_secret_key_t*)key2;
    int ret = picoquic_compare_addr((struct sockaddr*) & net_secret1->saddr, (struct sockaddr*) & net_secret2->saddr);

    if (ret == 0) {
#ifdef PICOQUIC_USE_CONSTANT_TIME_MEMCMP
        ret = picoquic_constant_time_memcmp(net_secret1->reset_secret, net_secret2->reset_secret, PICOQUIC_RESET_SECRET_SIZE);
#else
        ret = memcmp(net_secret1->reset_secret, net_secret2->reset_secret, PICOQUIC_RESET_SECRET_SIZE);
#endif
    }
    return ret;
}

picoquic_packet_context_enum picoquic_context_from_epoch(int epoch)
{
    static picoquic_packet_context_enum const pc[4] = {
        picoquic_packet_context_initial,
        picoquic_packet_context_application,
        picoquic_packet_context_handshake,
        picoquic_packet_context_application
    };

    return (epoch >= 0 && epoch < 4) ? pc[epoch] : 0;
}

/* Token reuse management */

static int64_t picoquic_registered_token_compare(void* l, void* r)
{
    /* STream values are from 0 to 2^62-1, which means we are not worried with rollover */
    picoquic_registered_token_t* rt_l = (picoquic_registered_token_t*)l;
    picoquic_registered_token_t* rt_r = (picoquic_registered_token_t*)r;
    int64_t ret = rt_l->token_time - rt_r->token_time;
    if (ret == 0) {
        ret = rt_l->token_hash - rt_r->token_hash;
    }
    return ret;
}

static picosplay_node_t* picoquic_registered_token_create(void* value)
{
    return &((picoquic_registered_token_t*)value)->registered_token_node;
}


static void* picoquic_registered_token_value(picosplay_node_t* node)
{
    return (void*)((char*)node - offsetof(struct st_picoquic_registered_token_t, registered_token_node));
}

static void picoquic_registered_token_delete(void* tree, picosplay_node_t* node)
{
    picoquic_registered_token_t* rt = (picoquic_registered_token_t*)picoquic_registered_token_value(node);
    free(rt);
}

int picoquic_registered_token_check_reuse(picoquic_quic_t * quic,
    const uint8_t * token, size_t token_length, uint64_t expiry_time)
{
    int ret = -1;
    if (token_length >= 8) {
        picoquic_registered_token_t* rt = (picoquic_registered_token_t*)malloc(sizeof(picoquic_registered_token_t));
        if (rt != NULL) {
            picosplay_node_t* rt_n = NULL;
            memset(rt, 0, sizeof(picoquic_registered_token_t));
            rt->token_time = expiry_time;
            rt->token_hash = PICOPARSE_64(token + token_length - 8);
            rt->count = 1;
            rt_n = picosplay_find(&quic->token_reuse_tree, rt);
            if (rt_n != NULL) {
                free(rt);
                rt = (picoquic_registered_token_t*)picoquic_registered_token_value(rt_n);
                rt->count++;
                DBG_PRINTF("Token reuse detected, count=%d", rt->count);
            }
            else {
                (void)picosplay_insert(&quic->token_reuse_tree, rt);
                ret = 0;
            }
        }
    }

    return ret;
}

void picoquic_registered_token_clear(picoquic_quic_t* quic, uint64_t expiry_time_max)
{
    int end_reached = 0;
    do {
        picoquic_registered_token_t* rt_first = (picoquic_registered_token_t*)
            picoquic_registered_token_value(picosplay_first(&quic->token_reuse_tree));
        if (rt_first == NULL || rt_first->token_time >= expiry_time_max) {
            end_reached = 1;
        }
        else {
            picosplay_delete_hint(&quic->token_reuse_tree, &rt_first->registered_token_node);
        }
    } while (!end_reached);
}

/* Forward reference */
static void picoquic_wake_list_init(picoquic_quic_t* quic);

/* QUIC context create and dispose */
picoquic_quic_t* picoquic_create(uint32_t nb_connections,
    char const* cert_file_name,
    char const* key_file_name, 
    char const * cert_root_file_name,
    char const* default_alpn,
    picoquic_stream_data_cb_fn default_callback_fn,
    void* default_callback_ctx,
    picoquic_connection_id_cb_fn cnx_id_callback,
    void* cnx_id_callback_ctx,
    uint8_t reset_seed[PICOQUIC_RESET_SECRET_SIZE],
    uint64_t current_time,
    uint64_t* p_simulated_time,
    char const* ticket_file_name,
    const uint8_t* ticket_encryption_key,
    size_t ticket_encryption_key_length)
{
    picoquic_quic_t* quic = (picoquic_quic_t*)malloc(sizeof(picoquic_quic_t));
    int ret = 0;

    if (quic != NULL) {
        /* TODO: winsock init */
        /* TODO: open UDP sockets - maybe */
        memset(quic, 0, sizeof(picoquic_quic_t));

        quic->default_callback_fn = default_callback_fn;
        quic->default_callback_ctx = default_callback_ctx;
        quic->default_congestion_alg = PICOQUIC_DEFAULT_CONGESTION_ALGORITHM;
        quic->default_alpn = picoquic_string_duplicate(default_alpn);
        quic->cnx_id_callback_fn = cnx_id_callback;
        quic->cnx_id_callback_ctx = cnx_id_callback_ctx;
        quic->p_simulated_time = p_simulated_time;
        quic->local_cnxid_length = 8; /* TODO: should be lower on clients-only implementation */
        quic->padding_multiple_default = 0; /* TODO: consider default = 128 */
        quic->padding_minsize_default = PICOQUIC_RESET_PACKET_MIN_SIZE;
        quic->crypto_epoch_length_max = 0;
        quic->max_simultaneous_logs = PICOQUIC_DEFAULT_SIMULTANEOUS_LOGS;
        quic->max_half_open_before_retry = PICOQUIC_DEFAULT_HALF_OPEN_RETRY_THRESHOLD;
        picoquic_wake_list_init(quic);

        if (cnx_id_callback != NULL) {
            quic->unconditional_cnx_id = 1;
        }

        if (ticket_file_name != NULL) {
            quic->ticket_file_name = ticket_file_name;
            ret = picoquic_load_tickets(&quic->p_first_ticket, current_time, ticket_file_name);

            if (ret == PICOQUIC_ERROR_NO_SUCH_FILE) {
                DBG_PRINTF("Ticket file <%s> not created yet.\n", ticket_file_name);
                ret = 0;
            }
            else if (ret != 0) {
                DBG_PRINTF("Cannot load tickets from <%s>\n", ticket_file_name);
                ret = 0;
            }
        }

        if (ret == 0) {
            quic->table_cnx_by_id = picohash_create((size_t)nb_connections * 4,
                picoquic_cnx_id_hash, picoquic_cnx_id_compare);

            quic->table_cnx_by_net = picohash_create((size_t)nb_connections * 4,
                picoquic_net_id_hash, picoquic_net_id_compare);

            quic->table_cnx_by_icid = picohash_create((size_t)nb_connections,
                picoquic_net_icid_hash, picoquic_net_icid_compare);

            quic->table_cnx_by_secret = picohash_create((size_t)nb_connections * 4,
                picoquic_net_secret_hash, picoquic_net_secret_compare);

            picosplay_init_tree(&quic->token_reuse_tree, picoquic_registered_token_compare,
                picoquic_registered_token_create, picoquic_registered_token_delete, picoquic_registered_token_value);

            if (quic->table_cnx_by_id == NULL || quic->table_cnx_by_net == NULL ||
                quic->table_cnx_by_icid == NULL || quic->table_cnx_by_secret == NULL) {
                ret = -1;
                DBG_PRINTF("%s", "Cannot initialize hash tables\n");
            }
            else if (picoquic_master_tlscontext(quic, cert_file_name, key_file_name, cert_root_file_name, ticket_encryption_key, ticket_encryption_key_length) != 0) {
                ret = -1;
                DBG_PRINTF("%s", "Cannot create TLS context \n");
            }
            else {
                /* the random generator was initialized as part of the TLS context.
                 * Use it to create the seed for generating the per context stateless
                 * resets and the retry tokens */

                if (!reset_seed)
                    picoquic_crypto_random(quic, quic->reset_seed, sizeof(quic->reset_seed));
                else
                    memcpy(quic->reset_seed, reset_seed, sizeof(quic->reset_seed));

                picoquic_crypto_random(quic, quic->retry_seed, sizeof(quic->retry_seed));

                /* If there is no root certificate context specified, use a null certifier. */
            }
        }
        
        if (ret != 0) {
            picoquic_free(quic);
            quic = NULL;
        }
    }

    return quic;
}

int picoquic_load_token_file(picoquic_quic_t* quic, char const * token_file_name)
{
    uint64_t current_time = picoquic_get_quic_time(quic);
    int ret = picoquic_load_tokens(&quic->p_first_token, current_time, token_file_name);

    if (ret == PICOQUIC_ERROR_NO_SUCH_FILE) {
        DBG_PRINTF("Ticket file <%s> not created yet.\n", token_file_name);
        ret = 0;
    }
    else if (ret != 0) {
        DBG_PRINTF("Cannot load tickets from <%s>\n", token_file_name);
    }

    if (ret == 0) {
        quic->token_file_name = token_file_name;
    }

    return ret;
}

int picoquic_set_default_tp(picoquic_quic_t* quic, picoquic_tp_t * tp)
{
    int ret = 0;

    if (quic->default_tp == NULL) {
        quic->default_tp = (picoquic_tp_t *)malloc(sizeof(picoquic_tp_t));
    }

    if (quic->default_tp == NULL) {
        ret = PICOQUIC_ERROR_MEMORY;
    }
    else {
        memcpy(quic->default_tp, tp, sizeof(picoquic_tp_t));
    }

    return ret;
}

void picoquic_set_default_padding(picoquic_quic_t* quic, uint32_t padding_multiple, uint32_t padding_minsize)
{
    quic->padding_minsize_default = padding_minsize;
    quic->padding_multiple_default = padding_multiple;
}

void picoquic_set_default_spinbit_policy(picoquic_quic_t * quic, picoquic_spinbit_version_enum default_spinbit_policy)
{
    quic->default_spin_policy = default_spinbit_policy;
}

void picoquic_set_default_crypto_epoch_length(picoquic_quic_t* quic, uint64_t crypto_epoch_length_max)
{
    quic->crypto_epoch_length_max = (crypto_epoch_length_max == 0) ?
        PICOQUIC_DEFAULT_CRYPTO_EPOCH_LENGTH : crypto_epoch_length_max;
}

uint64_t picoquic_get_default_crypto_epoch_length(picoquic_quic_t* quic)
{
    return quic->crypto_epoch_length_max;
}

void picoquic_set_crypto_epoch_length(picoquic_cnx_t* cnx, uint64_t crypto_epoch_length_max)
{
    cnx->crypto_epoch_length_max = (crypto_epoch_length_max == 0) ?
        PICOQUIC_DEFAULT_CRYPTO_EPOCH_LENGTH : crypto_epoch_length_max;
}

uint64_t picoquic_get_crypto_epoch_length(picoquic_cnx_t* cnx)
{
    return cnx->crypto_epoch_length_max;
}


uint8_t picoquic_get_local_cid_length(picoquic_quic_t* quic)
{
    return quic->local_cnxid_length;
}

int picoquic_is_local_cid(picoquic_quic_t* quic, picoquic_connection_id_t* cid)
{
    return (cid->id_len == quic->local_cnxid_length &&
        picoquic_cnx_by_id(quic, *cid) != NULL);
}

void picoquic_set_max_simultaneous_logs(picoquic_quic_t* quic, uint32_t max_simultaneous_logs)
{
    quic->max_simultaneous_logs = max_simultaneous_logs;
}

uint32_t picoquic_get_max_simultaneous_logs(picoquic_quic_t* quic)
{
    return quic->max_simultaneous_logs;
}

void picoquic_free(picoquic_quic_t* quic)
{
    if (quic != NULL) {

        /* delete all the connection contexts -- do this before any other
         * action, as deleting connections may add packets to queues or
         * change connection lists */
        while (quic->cnx_list != NULL) {
            picoquic_delete_cnx(quic->cnx_list);
        }

        /* Delete TLS and AEAD cntexts */
        picoquic_delete_retry_protection_contexts(quic);

        if (quic->aead_encrypt_ticket_ctx != NULL) {
            picoquic_aead_free(quic->aead_encrypt_ticket_ctx);
            quic->aead_encrypt_ticket_ctx = NULL;
        }

        if (quic->aead_decrypt_ticket_ctx != NULL) {
            picoquic_aead_free(quic->aead_decrypt_ticket_ctx);
            quic->aead_decrypt_ticket_ctx = NULL;
        }

        if (quic->default_alpn != NULL) {
            free((void*)quic->default_alpn);
            quic->default_alpn = NULL;
        }

        /* delete the stored tickets */
        picoquic_free_tickets(&quic->p_first_ticket);

        /* Deelete the reused tokens tree */
        picosplay_empty_tree(&quic->token_reuse_tree);

        /* delete packets in pool */
        while (quic->p_first_packet != NULL) {
            picoquic_packet_t * p = quic->p_first_packet->next_packet;
            free(quic->p_first_packet);
            quic->p_first_packet = p;
        }

        /* delete all pending stateless packets */
        while (quic->pending_stateless_packet != NULL) {
            picoquic_stateless_packet_t* to_delete = quic->pending_stateless_packet;
            quic->pending_stateless_packet = to_delete->next_packet;
            free(to_delete);
        }

        if (quic->table_cnx_by_id != NULL) {
            picohash_delete(quic->table_cnx_by_id, 1);
        }

        if (quic->table_cnx_by_net != NULL) {
            picohash_delete(quic->table_cnx_by_net, 1);
        }

        if (quic->table_cnx_by_icid != NULL) {
            picohash_delete(quic->table_cnx_by_icid, 1);
        }

        if (quic->table_cnx_by_secret != NULL) {
            picohash_delete(quic->table_cnx_by_secret, 1);
        }

        if (quic->verify_certificate_ctx != NULL &&
            quic->free_verify_certificate_callback_fn != NULL) {
            (quic->free_verify_certificate_callback_fn)(quic->verify_certificate_ctx);
            quic->verify_certificate_ctx = NULL;
        }

        if (quic->verify_certificate_callback_fn != NULL) {
            picoquic_dispose_verify_certificate_callback(quic, 1);
        }

        if (quic->default_tp != NULL) {
            free(quic->default_tp);
            quic->default_tp = NULL;
        }

        /* Delete the picotls context */
        if (quic->tls_master_ctx != NULL) {
            picoquic_master_tlscontext_free(quic);

            free(quic->tls_master_ctx);
            quic->tls_master_ctx = NULL;
        }

        quic->binlog_dir = picoquic_string_free(quic->binlog_dir);
        quic->qlog_dir = picoquic_string_free(quic->qlog_dir);

        free(quic);
    }
}

void picoquic_set_null_verifier(picoquic_quic_t* quic) {
    picoquic_dispose_verify_certificate_callback(quic, quic->verify_certificate_callback_fn != NULL);
}

void picoquic_set_cookie_mode(picoquic_quic_t* quic, int cookie_mode)
{
    if (cookie_mode&1) {
        quic->force_check_token = 1;
    } else {
        quic->force_check_token = 0;
    }

    if (cookie_mode & 2) {
        quic->provide_token = 1;
    }
    else {
        quic->provide_token = 0;
    }

    quic->check_token = (quic->force_check_token || quic->max_half_open_before_retry <= quic->current_number_half_open);
}

void picoquic_set_max_half_open_retry_threshold(picoquic_quic_t* quic,  uint32_t max_half_open_before_retry)
{
    quic->max_half_open_before_retry = max_half_open_before_retry;
}

uint32_t picoquic_get_max_half_open_retry_threshold(picoquic_quic_t* quic)
{
    return quic->max_half_open_before_retry;
}

picoquic_stateless_packet_t* picoquic_create_stateless_packet(picoquic_quic_t* quic)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(quic);
#endif
    return (picoquic_stateless_packet_t*)malloc(sizeof(picoquic_stateless_packet_t));
}

void picoquic_delete_stateless_packet(picoquic_stateless_packet_t* sp)
{
    free(sp);
}

void picoquic_queue_stateless_packet(picoquic_quic_t* quic, picoquic_stateless_packet_t* sp)
{
    picoquic_stateless_packet_t** pnext = &quic->pending_stateless_packet;

    while ((*pnext) != NULL) {
        pnext = &(*pnext)->next_packet;
    }

    *pnext = sp;
    sp->next_packet = NULL;
}

picoquic_stateless_packet_t* picoquic_dequeue_stateless_packet(picoquic_quic_t* quic)
{
    picoquic_stateless_packet_t* sp = quic->pending_stateless_packet;

    if (sp != NULL) {
        quic->pending_stateless_packet = sp->next_packet;
        sp->next_packet = NULL;
        picoquic_log_quic_pdu(quic, 0, picoquic_get_quic_time(quic), sp->cnxid_log64,
            (struct sockaddr*) & sp->addr_to, (struct sockaddr*) & sp->addr_local, sp->length);
    }

    return sp;
}

int picoquic_cnx_is_still_logging(picoquic_cnx_t* cnx)
{
    int ret =
        (cnx->nb_packets_logged < PICOQUIC_LOG_PACKET_MAX_SEQUENCE || cnx->quic->use_long_log);

    return ret;
}

/* Connection context creation and registration */
int picoquic_register_cnx_id(picoquic_quic_t* quic, picoquic_cnx_t* cnx, picoquic_local_cnxid_t* l_cid)
{
    int ret = 0;
    picohash_item* item;
    picoquic_cnx_id_key_t* key = (picoquic_cnx_id_key_t*)malloc(sizeof(picoquic_cnx_id_key_t));

    if (key == NULL) {
        ret = -1;
    } else {
        key->cnx_id = l_cid->cnx_id;
        key->cnx = cnx;
        key->l_cid = l_cid;
        key->next_cnx_id = NULL;

        item = picohash_retrieve(quic->table_cnx_by_id, key);

        if (item != NULL) {
            ret = -1;
        } else {
            ret = picohash_insert(quic->table_cnx_by_id, key);

            if (ret == 0) {
                key->next_cnx_id = l_cid->first_cnx_id;
                l_cid->first_cnx_id = key;
            }
        }
    }

    return ret;
}

int picoquic_register_net_id(picoquic_quic_t* quic, picoquic_cnx_t* cnx, picoquic_path_t * path_x, struct sockaddr* addr)
{
    int ret = 0;
    picohash_item* item;
    picoquic_net_id_key_t* key = (picoquic_net_id_key_t*)malloc(sizeof(picoquic_net_id_key_t));

    if (key == NULL) {
        ret = -1;
    } else {
        memset(key, 0, sizeof(picoquic_net_id_key_t));
        picoquic_store_addr(&key->saddr, addr);

        key->cnx = cnx;
        key->path = path_x;

        item = picohash_retrieve(quic->table_cnx_by_net, key);

        if (item != NULL) {
            ret = -1;
        } else {
            ret = picohash_insert(quic->table_cnx_by_net, key);

            if (ret == 0) {
                key->next_net_id = path_x->first_net_id;
                path_x->first_net_id = key;
            }
        }
    }

    if (key != NULL && ret != 0) {
        free(key);
    }

    return ret;
}

/* The initial CID and the reset secret are tracked in specific tables:
 *
 * - quic->table_cnx_by_icid: keyed by client address and initial CID. Created
 *   when the connection for the specified initial CID and address is created
 *   by the server (or the peer receiving the connection in P2P cases)
 * - quic->table_cnx_by_secret: keyed by peer address and reset secret for
 *   the default path of the connection (cnx->path[0]).
 *
 * In both cases, the address is that associated to the default path. The
 * path can be updated after migration, either by an address change or by
 * a change of CID and secret while keeping the address constant.
 *
 * If either the default address or the default reset secret changes, the
 * old table entry is updated to track the new address and secret. The
 * entry is kept up to date until the connection closes.
 *
 * Migration can only happen after a connection is established, but
 * packets could still arrive after that, maybe due to network delays.
 * In order to keep the design simple, the ICID entry is created once, and
 * kept for the duration of the connection.
 *
 * To facilitate management, the hash table keys are remembered in the
 * connection context as:
 *
 * - cnx->reset_secret_key
 * - cnx->net_icid_key
 */

int picoquic_register_net_icid(picoquic_cnx_t* cnx)
{
    int ret = 0;
    picohash_item* item;
    picoquic_net_icid_key_t* key = (picoquic_net_icid_key_t*)malloc(sizeof(picoquic_net_icid_key_t));

    if (key == NULL) {
        ret = -1;
    }
    else {
        memset(key, 0, sizeof(picoquic_net_icid_key_t));
        picoquic_store_addr(&key->saddr, (struct sockaddr *)&cnx->path[0]->peer_addr);
        key->icid = cnx->initial_cnxid;

        key->cnx = cnx;

        item = picohash_retrieve(cnx->quic->table_cnx_by_icid, key);

        if (item != NULL) {
            ret = -1;
        }
        else {
            ret = picohash_insert(cnx->quic->table_cnx_by_icid, key);

            if (ret == 0) {
                cnx->net_icid_key = key;
            }
        }
    }

    if (key != NULL && ret != 0) {
        free(key);
    }

    return ret;
}

int picoquic_register_net_secret(picoquic_cnx_t* cnx)
{
    int ret = 0;
    picohash_item* item;
    picoquic_net_secret_key_t* key = (picoquic_net_secret_key_t*)malloc(sizeof(picoquic_net_secret_key_t));

    if (key == NULL) {
        ret = PICOQUIC_ERROR_MEMORY;
    }
    else {
        memset(key, 0, sizeof(picoquic_net_secret_key_t));
        picoquic_store_addr(&key->saddr, (struct sockaddr *)&cnx->path[0]->peer_addr);
        memcpy(key->reset_secret, cnx->path[0]->reset_secret, PICOQUIC_RESET_SECRET_SIZE);

        key->cnx = cnx;

        item = picohash_retrieve(cnx->quic->table_cnx_by_secret, key);

        if (item != NULL) {
            ret = -1;
        } 
        else {
            ret = picohash_insert(cnx->quic->table_cnx_by_secret, key);
            
            if (ret == 0) {
                if (cnx->reset_secret_key != NULL) {
                    picohash_delete_key(cnx->quic->table_cnx_by_secret, cnx->reset_secret_key, 1);
                }
                cnx->reset_secret_key = key;
            }
        }
    }

    if (key != NULL && ret != 0) {
        free(key);
    }

    return ret;
}

void picoquic_init_transport_parameters(picoquic_tp_t* tp, int client_mode)
{
    tp->initial_max_stream_data_bidi_local = 0x200000;
    tp->initial_max_stream_data_bidi_remote = 65635;
    tp->initial_max_stream_data_uni = 65535;
    tp->initial_max_data = 0x100000;
    if (client_mode) {
        tp->initial_max_stream_id_bidir = 2049;
        tp->initial_max_stream_id_unidir = 2051;
    } else {
        tp->initial_max_stream_id_bidir = 2048;
        tp->initial_max_stream_id_unidir = 2050;
    }
    tp->idle_timeout = PICOQUIC_MICROSEC_HANDSHAKE_MAX/1000;
    tp->max_packet_size = PICOQUIC_PRACTICAL_MAX_MTU;
    tp->max_datagram_frame_size = 0;
    tp->ack_delay_exponent = 3;
    tp->active_connection_id_limit = PICOQUIC_NB_PATH_TARGET;
    tp->max_ack_delay = PICOQUIC_ACK_DELAY_MAX;
    tp->enable_loss_bit = 2;
    tp->min_ack_delay = PICOQUIC_ACK_DELAY_MIN;
    tp->enable_time_stamp = 0;
}


/* management of the list of connections in context */

picoquic_quic_t* picoquic_get_quic_ctx(picoquic_cnx_t* cnx)
{
    return (cnx == NULL)?NULL:cnx->quic;
}

picoquic_cnx_t* picoquic_get_first_cnx(picoquic_quic_t* quic)
{
    return quic->cnx_list;
}

picoquic_cnx_t* picoquic_get_next_cnx(picoquic_cnx_t* cnx)
{
    return cnx->next_in_table;
}

static void picoquic_insert_cnx_in_list(picoquic_quic_t* quic, picoquic_cnx_t* cnx)
{
    if (quic->cnx_list != NULL) {
        quic->cnx_list->previous_in_table = cnx;
        cnx->next_in_table = quic->cnx_list;
    } else {
        quic->cnx_last = cnx;
        cnx->next_in_table = NULL;
    }
    quic->cnx_list = cnx;
    cnx->previous_in_table = NULL;
}

static void picoquic_remove_cnx_from_list(picoquic_cnx_t* cnx)
{
    if (cnx->next_in_table == NULL) {
        cnx->quic->cnx_last = cnx->previous_in_table;
    } else {
        cnx->next_in_table->previous_in_table = cnx->previous_in_table;
    }

    if (cnx->previous_in_table == NULL) {
        cnx->quic->cnx_list = cnx->next_in_table;
    }
    else {
        cnx->previous_in_table->next_in_table = cnx->next_in_table;
    }

    if (cnx->net_icid_key != NULL) {
        picohash_delete_key(cnx->quic->table_cnx_by_icid, cnx->net_icid_key, 1);
        cnx->net_icid_key = NULL;
    }

    if (cnx->reset_secret_key != NULL) {
        picohash_delete_key(cnx->quic->table_cnx_by_secret, cnx->reset_secret_key, 1);
        cnx->reset_secret_key = NULL;
    }
}

/* Management of the list of connections, sorted by wake time */

static void* picoquic_wake_list_node_value(picosplay_node_t* cnx_wake_node)
{
    return (cnx_wake_node == NULL)?NULL:(void*)((char*)cnx_wake_node - offsetof(struct st_picoquic_cnx_t, cnx_wake_node));
}

static int64_t picoquic_wake_list_compare(void* l, void* r) {
    return (int64_t)((picoquic_cnx_t*)l)->next_wake_time - ((picoquic_cnx_t*)r)->next_wake_time;
}

static picosplay_node_t* picoquic_wake_list_create_node(void* v_cnx)
{
    return &((picoquic_cnx_t*)v_cnx)->cnx_wake_node;
}

static void picoquic_wake_list_delete_node(void* tree, picosplay_node_t* node)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(tree);
#endif
    memset(node, 0, sizeof(picosplay_node_t));
}

static void picoquic_wake_list_init(picoquic_quic_t * quic)
{
    picosplay_init_tree(&quic->cnx_wake_tree, picoquic_wake_list_compare,
        picoquic_wake_list_create_node, picoquic_wake_list_delete_node, picoquic_wake_list_node_value);
}

static void picoquic_remove_cnx_from_wake_list(picoquic_cnx_t* cnx)
{
    picosplay_delete_hint(&cnx->quic->cnx_wake_tree, &cnx->cnx_wake_node);
}

static void picoquic_insert_cnx_by_wake_time(picoquic_quic_t* quic, picoquic_cnx_t* cnx)
{
    picosplay_insert(&quic->cnx_wake_tree, cnx);
}

void picoquic_reinsert_by_wake_time(picoquic_quic_t* quic, picoquic_cnx_t* cnx, uint64_t next_time)
{
    picoquic_remove_cnx_from_wake_list(cnx);
    cnx->next_wake_time = next_time;
    picoquic_insert_cnx_by_wake_time(quic, cnx);
}

picoquic_cnx_t* picoquic_get_earliest_cnx_to_wake(picoquic_quic_t* quic, uint64_t max_wake_time)
{
    picoquic_cnx_t* cnx = (picoquic_cnx_t *)picoquic_wake_list_node_value(picosplay_first(&quic->cnx_wake_tree));
    if (cnx != NULL && max_wake_time != 0 && cnx->next_wake_time > max_wake_time)
    {
        cnx = NULL;
    }

    return cnx;
}

uint64_t picoquic_get_next_wake_time(picoquic_quic_t* quic, uint64_t current_time)
{
    uint64_t wake_time = UINT64_MAX;

    if (quic->pending_stateless_packet != NULL) {
        wake_time = current_time;
    }
    else{
        picoquic_cnx_t* cnx_wake_first = (picoquic_cnx_t*)picoquic_wake_list_node_value(
            picosplay_first(&quic->cnx_wake_tree));

        if (cnx_wake_first != NULL) {
            wake_time = cnx_wake_first->next_wake_time;
        }
    }

    return wake_time;
}

int64_t picoquic_get_next_wake_delay(picoquic_quic_t* quic,
    uint64_t current_time, int64_t delay_max)
{
    uint64_t next_wake_time = picoquic_get_next_wake_time(quic, current_time);
    int64_t wake_delay = next_wake_time - current_time;

    if (wake_delay > delay_max || next_wake_time == UINT64_MAX) {
        wake_delay = delay_max;
    }

    return wake_delay;
}

/* Other context management functions */

int picoquic_get_version_index(uint32_t proposed_version)
{
    int ret = -1;

    for (size_t i = 0; i < picoquic_nb_supported_versions; i++) {
        if (picoquic_supported_versions[i].version == proposed_version) {
            ret = (int)i;
            break;
        }
    }

    return ret;
}


static void picoquic_create_random_cnx_id(picoquic_quic_t* quic, picoquic_connection_id_t * cnx_id, uint8_t id_length)
{
    if (id_length > 0) {
        picoquic_crypto_random(quic, cnx_id->id, id_length);
    }
    if (id_length < sizeof(cnx_id->id)) {
        memset(cnx_id->id + id_length, 0, sizeof(cnx_id->id) - id_length);
    }
    cnx_id->id_len = id_length;
}

/* Path management -- returns the index of the path that was created. */

int picoquic_create_path(picoquic_cnx_t* cnx, uint64_t start_time, const struct sockaddr* local_addr, const struct sockaddr* peer_addr)
{
    int ret = -1;

    if (cnx->nb_paths >= cnx->nb_path_alloc)
    {
        int new_alloc = (cnx->nb_path_alloc == 0) ? 1 : 2 * cnx->nb_path_alloc;
        picoquic_path_t ** new_path = (picoquic_path_t **)malloc(new_alloc * sizeof(picoquic_path_t *));

        if (new_path != NULL)
        {
            if (cnx->path != NULL)
            {
                if (cnx->nb_paths > 0)
                {
                    memcpy(new_path, cnx->path, cnx->nb_paths * sizeof(picoquic_path_t *));
                }
                free(cnx->path);
            }
            cnx->path = new_path;
            cnx->nb_path_alloc = new_alloc;
        }
    }

    if (cnx->nb_paths < cnx->nb_path_alloc)
    {
        picoquic_path_t * path_x = (picoquic_path_t *)malloc(sizeof(picoquic_path_t));

        if (path_x != NULL)
        {
            memset(path_x, 0, sizeof(picoquic_path_t));
            /* Register the sequence number */
            path_x->path_sequence = cnx->path_sequence_next;
            cnx->path_sequence_next++;

            /* Set the addresses */
            picoquic_store_addr(&path_x->peer_addr, peer_addr);
            picoquic_store_addr(&path_x->local_addr, local_addr);

            /* Set the challenge used for this path */
            for (int ichal = 0; ichal < PICOQUIC_CHALLENGE_REPEAT_MAX; ichal++) {
                path_x->challenge[ichal] = picoquic_public_random_64();
            }

            /* Initialize the reset secret to a random value. This
            * will prevent spurious matches to an all zero value, for example.
            * The real value will be set when receiving the transport parameters.
            */
            picoquic_public_random(path_x->reset_secret, PICOQUIC_RESET_SECRET_SIZE);

            /* Initialize per path time measurement */
            path_x->smoothed_rtt = PICOQUIC_INITIAL_RTT;
            path_x->rtt_variant = 0;
            path_x->retransmit_timer = PICOQUIC_INITIAL_RETRANSMIT_TIMER;
            path_x->rtt_min = 0;

            /* Initialize per path congestion control state */
            path_x->cwin = PICOQUIC_CWIN_INITIAL;
            path_x->bytes_in_transit = 0;
            path_x->congestion_alg_state = NULL;

            /* Initialize per path pacing state */
            path_x->pacing_evaluation_time = start_time;
            path_x->pacing_bucket_nanosec = 16;
            path_x->pacing_bucket_max = 16;
            path_x->pacing_packet_time_nanosec = 1;
            path_x->pacing_packet_time_microsec = 1;

            /* Initialize the MTU */
            path_x->send_mtu = (peer_addr == NULL || peer_addr->sa_family == AF_INET) ? PICOQUIC_INITIAL_MTU_IPV4 : PICOQUIC_INITIAL_MTU_IPV6;

            /* Record the path */
            cnx->path[cnx->nb_paths] = path_x;
            ret = cnx->nb_paths++;
        }
    }

    return ret;
}

/*
 * Register the path in the hash tables.
 * This only registers the address associated with the path.
 */
void picoquic_register_path(picoquic_cnx_t* cnx, picoquic_path_t * path_x)
{

    if (path_x->peer_addr.ss_family != 0) {
        (void)picoquic_register_net_id(cnx->quic, cnx, cnx->path[0], (struct sockaddr *)&path_x->peer_addr);
    }

    path_x->path_is_registered = 1;
}

/* To delete a path, we need to delete the data allocated to the path: search items in
 * the hash tables, and congestion algorithm context. Then delete the path data itself,
 * and finally remove the path reference from the table of paths in the connection
 * context.
 */

static void picoquic_clear_path_data(picoquic_cnx_t* cnx, picoquic_path_t * path_x) 
{
    while (path_x->first_net_id != NULL) {
        picohash_item* item;
        picoquic_net_id_key_t* net_id_key = path_x->first_net_id;
        path_x->first_net_id = net_id_key->next_net_id;
        net_id_key->next_net_id = NULL;

        item = picohash_retrieve(cnx->quic->table_cnx_by_net, net_id_key);
        if (item != NULL) {
            picohash_delete_item(cnx->quic->table_cnx_by_net, item, 1);
        }
    }
    /* Remove the congestion data */
    if (cnx->congestion_alg != NULL) {
        cnx->congestion_alg->alg_delete(path_x);
    }

    /* Free the record */
    free(path_x);
}

void picoquic_delete_path(picoquic_cnx_t* cnx, int path_index)
{
    picoquic_path_t * path_x = cnx->path[path_index];
    picoquic_packet_t* p = NULL;

    if (cnx->quic->F_log != NULL) {
        fflush(cnx->quic->F_log);
    }
        
    /* Remove old path data from retransmit queue */
    for (picoquic_packet_context_enum pc = 0; pc < picoquic_nb_packet_context; pc++)
    {
        p = cnx->pkt_ctx[pc].retransmit_newest;

        while (p != NULL) {
            if (p->send_path == path_x) {
                DBG_PRINTF("Erase path for packet pc: %d, seq:%" PRIu64 "\n", pc, p->sequence_number);
                p->send_path = NULL;
            }
            p = p->next_packet;
        }

        p = cnx->pkt_ctx[pc].retransmitted_newest;
        while (p != NULL) {
            if (p->send_path == path_x) {
                DBG_PRINTF("Erase path for old packet pc: %d, seq:%" PRIu64 "\n", pc, p->sequence_number);
                p->send_path = NULL;
            }
            p = p->next_packet;
        }
    }
    /* Free the data */
    picoquic_clear_path_data(cnx, path_x);


    /* Compact the path table  */
    for (int i = path_index + 1; i < cnx->nb_paths; i++) {
        cnx->path[i-1] = cnx->path[i];
    }

    cnx->nb_paths--;
    cnx->path[cnx->nb_paths] = NULL;
}

/*
 * Path challenges may be abandoned if they are tried too many times without success. 
 */

void picoquic_delete_abandoned_paths(picoquic_cnx_t* cnx, uint64_t current_time, uint64_t * next_wake_time)
{
    int path_index_good = 1;
    int path_index_current = 1;
    unsigned int is_demotion_in_progress = 0;

    while (path_index_current < cnx->nb_paths) {
        if (cnx->path[path_index_current]->challenge_failed ||
            (cnx->path[path_index_current]->path_is_demoted &&
                current_time >= cnx->path[path_index_current]->demotion_time) ||
            (path_index_current > 0 && cnx->path[path_index_current]->challenge_verified &&
                current_time - cnx->path[path_index_current]->latest_sent_time >= cnx->idle_timeout)) {
            /* Demote any failed path */
            if (!cnx->path[path_index_current]->path_is_demoted) {
                picoquic_demote_path(cnx, path_index_current, current_time);
            }
            /* Only increment the current index */
            is_demotion_in_progress |= cnx->path[path_index_current]->path_is_demoted;
            path_index_current++;
        } else {
            if (cnx->path[path_index_current]->path_is_demoted &&
                current_time < cnx->path[path_index_current]->demotion_time){
                is_demotion_in_progress |= 1;
                if (*next_wake_time > cnx->path[path_index_current]->demotion_time) {
                    *next_wake_time = cnx->path[path_index_current]->demotion_time;
                    SET_LAST_WAKE(cnx->quic, PICOQUIC_QUICCTX);
                }
            }

            if (path_index_current > path_index_good) {
                /* swap the path indexed good with current */
                picoquic_path_t * path_x = cnx->path[path_index_current];
                cnx->path[path_index_current] = cnx->path[path_index_good];
                cnx->path[path_index_good] = path_x;
            }
            /* increment both indices */
            path_index_current++;
            path_index_good++;
        }
    }

    while (cnx->nb_paths > path_index_good) {
        int d_path = cnx->nb_paths - 1;
        if (!picoquic_is_connection_id_null(&cnx->path[d_path]->remote_cnxid)) {
            (void)picoquic_queue_retire_connection_id_frame(cnx, cnx->path[d_path]->remote_cnxid_sequence);
        }
        picoquic_delete_path(cnx, d_path);
    }

    /* TODO: what if there are no paths left? */
    cnx->path_demotion_needed = is_demotion_in_progress;
}

/* 
 * Demote path, compute the effective time for demotion.
 */
void picoquic_demote_path(picoquic_cnx_t* cnx, int path_index, uint64_t current_time)
{
    if (!cnx->path[path_index]->path_is_demoted) {
        uint64_t demote_timer = cnx->path[path_index]->retransmit_timer;

        if (demote_timer < PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER) {
            demote_timer = PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER;
        }

        cnx->path[path_index]->path_is_demoted = 1;
        cnx->path[path_index]->demotion_time = current_time + 3* demote_timer;
        cnx->path_demotion_needed = 1;
    }
}

/* Promote path to default. This happens when a new path is verified, at the end
 * of a migration, and becomes the new default path.
 */

void picoquic_promote_path_to_default(picoquic_cnx_t* cnx, int path_index, uint64_t current_time)
{
    if (path_index > 0 && path_index < cnx->nb_paths) {
        picoquic_path_t * path_x = cnx->path[path_index];

        if (cnx->path[path_index]->path_is_preferred_path) {
            /* this is a migration to the preferred path requested by the server */
            if (cnx->client_mode) {
                cnx->remote_parameters.migration_disabled = 0;
            }
            else {
                cnx->local_parameters.migration_disabled = 0;
            }
        }

        if (cnx->quic->F_log != NULL || cnx->f_binlog != NULL) {
            char src_ip[128];
            char dst_ip[128];

            picoquic_log_app_message(cnx, "Path %d promoted to default at T=%fs, Local: %s, Remote: %s",
                path_index, (double)(current_time - cnx->start_time) / 1000000.0,
                picoquic_addr_text((struct sockaddr*) & cnx->path[path_index]->local_addr, src_ip, sizeof(src_ip)),
                picoquic_addr_text((struct sockaddr*) & cnx->path[path_index]->peer_addr, dst_ip, sizeof(dst_ip)));
        }

        /* Set the congestion algorithm for the new path */
        if (cnx->congestion_alg != NULL) {
            cnx->congestion_alg->alg_init(path_x, current_time);
        }

        /* Mark old path as demoted */
        picoquic_demote_path(cnx, 0, current_time);

        /* Swap */
        cnx->path[path_index] = cnx->path[0];
        cnx->path[0] = path_x;

        /* Update the secret */
        (void)picoquic_register_net_secret(cnx);
    }
}

/* Set or renew challenge for a path */
void picoquic_set_path_challenge(picoquic_cnx_t* cnx, int path_id, uint64_t current_time)
{
    if (!cnx->path[path_id]->challenge_required || cnx->path[path_id]->challenge_verified) {
        /* Reset the path challenge */
        cnx->path[path_id]->challenge_required = 1;
        for (int ichal = 0; ichal < PICOQUIC_CHALLENGE_REPEAT_MAX; ichal++) {
            cnx->path[path_id]->challenge[ichal] = picoquic_public_random_64();
        }
        cnx->path[path_id]->challenge_verified = 0;
        cnx->path[path_id]->challenge_time = current_time;
        cnx->path[path_id]->challenge_repeat_count = 0;
    }
}

/* Find path by address pair
 */
int picoquic_find_path_by_address(picoquic_cnx_t* cnx, const struct sockaddr* addr_to, 
    const struct sockaddr* addr_from, int * partial_match)
{
    int path_id = -1;
    int is_null_from = 0;
    struct sockaddr_storage null_addr;

    *partial_match = -1;

    if (addr_from != NULL || addr_to != NULL) {
        if (addr_from == NULL || addr_to == NULL) {
            memset(&null_addr, 0, sizeof(struct sockaddr_storage));
            if (addr_from == NULL) {
                addr_from = (struct sockaddr*) & null_addr;
            }
            else {
                addr_to = (struct sockaddr*) & null_addr;
            }
            is_null_from = 1;
        }

        /* Find whether an existing path matches the  pair of addresses */
        for (int i = 0; i < cnx->nb_paths; i++) {
            if (picoquic_compare_addr((struct sockaddr*) & cnx->path[i]->peer_addr,
                addr_from) == 0) {
                if (cnx->path[i]->local_addr.ss_family == 0) {
                    *partial_match = i;
                }
                else if (picoquic_compare_addr((struct sockaddr*) & cnx->path[i]->local_addr,
                    addr_to) == 0) {
                    path_id = i;
                    break;
                }
            }

            if (path_id < 0 && is_null_from) {
                path_id = *partial_match;
                *partial_match = -1;
            }
        }
    }

    return path_id;
}

/* Assign CID to path */
int picoquic_assign_peer_cnxid_to_path(picoquic_cnx_t* cnx, int path_id)
{
    int ret = -1;
    picoquic_cnxid_stash_t* available_cnxid = picoquic_dequeue_cnxid_stash(cnx);

    if (available_cnxid != NULL) {
        cnx->path[path_id]->remote_cnxid = available_cnxid->cnx_id;
        cnx->path[path_id]->remote_cnxid_sequence = available_cnxid->sequence;
        memcpy(cnx->path[path_id]->reset_secret, available_cnxid->reset_secret,
            PICOQUIC_RESET_SECRET_SIZE);
        free(available_cnxid);
        ret = 0;
    }

    return ret;
}

/* Create a new path in order to trigger a migration */
int picoquic_probe_new_path_ex(picoquic_cnx_t* cnx, const struct sockaddr* addr_from,
    const struct sockaddr* addr_to, uint64_t current_time, int to_preferred_address)
{
    int ret = 0;
    int partial_match_path = -1;
    int path_id = -1;

    if ((cnx->remote_parameters.migration_disabled && !to_preferred_address ) ||
        cnx->local_parameters.migration_disabled) {
        /* Do not create new paths if migration is disabled */
        ret = PICOQUIC_ERROR_MIGRATION_DISABLED;
        DBG_PRINTF("Tried to create probe with migration disabled = %d", cnx->remote_parameters.migration_disabled);
    }
    else if ((path_id = picoquic_find_path_by_address(cnx, addr_to, addr_from, &partial_match_path)) >= 0) {
        /* This path already exists. Will not create it, but will restore it in working order if disabled. */
        ret = -1;
    }
    else if (partial_match_path >= 0 && addr_from->sa_family == 0) {
        /* This path already exists. Will not create it, but will restore it in working order if disabled. */
        ret = -1;
    }
    else if (cnx->cnxid_stash_first == NULL) {
        /* No CNXID available yet. */
        ret = -1;
    }
    else if (cnx->nb_paths >= PICOQUIC_NB_PATH_TARGET) {
        /* Too many paths created already */
        ret = -1;
    }
    else if (picoquic_create_path(cnx, current_time, addr_to, addr_from) > 0) {
        path_id = cnx->nb_paths - 1;
        ret = picoquic_assign_peer_cnxid_to_path(cnx, path_id);

        if (ret != 0) {
            /* delete the path that was just created! */
            picoquic_delete_path(cnx, path_id);
        }
        else {
            cnx->path[path_id]->path_is_published = 1;
            picoquic_register_path(cnx, cnx->path[path_id]);
            picoquic_set_path_challenge(cnx, path_id, current_time);
            cnx->path[path_id]->path_is_preferred_path = to_preferred_address;
        }
    }

    return ret;
}

int picoquic_probe_new_path(picoquic_cnx_t* cnx, const struct sockaddr* addr_from,
    const struct sockaddr* addr_to, uint64_t current_time)
{
    return picoquic_probe_new_path_ex(cnx, addr_from, addr_to, current_time, 0);
}

/* Reset the path MTU, for example if too many packet losses are detected */
void picoquic_reset_path_mtu(picoquic_path_t* path_x)
{
    /* Re-initialize the MTU */
    path_x->send_mtu = (path_x->peer_addr.ss_family == 0 || path_x->peer_addr.ss_family == AF_INET) ?
        PICOQUIC_INITIAL_MTU_IPV4 : PICOQUIC_INITIAL_MTU_IPV6;
    /* Reset the MTU discovery context */
    path_x->send_mtu_max_tried = 0;
    path_x->mtu_probe_sent = 0;
}

/*
 * Manage the stash of connection IDs sent by the peer 
 */

picoquic_cnxid_stash_t * picoquic_dequeue_cnxid_stash(picoquic_cnx_t * cnx)
{
    picoquic_cnxid_stash_t * stashed = NULL;

    if (cnx != NULL && cnx->cnxid_stash_first != NULL) {
        stashed = cnx->cnxid_stash_first;
        cnx->cnxid_stash_first = stashed->next_in_stash;
    }

    return stashed;
}

int picoquic_enqueue_cnxid_stash(picoquic_cnx_t* cnx,
    const uint64_t sequence, const uint8_t cid_length, const uint8_t* cnxid_bytes,
    const uint8_t* secret_bytes, picoquic_cnxid_stash_t** pstashed)
{
    int ret = 0;
    int is_duplicate = 0;
    size_t nb_cid_received = 0;
    picoquic_connection_id_t cnx_id;
    picoquic_cnxid_stash_t* next_stash = cnx->cnxid_stash_first;
    picoquic_cnxid_stash_t* last_stash = NULL;
    picoquic_cnxid_stash_t* stashed = NULL;
    uint64_t cnxid_mask = 0;

    /* verify the format */
    if (picoquic_parse_connection_id(cnxid_bytes, cid_length, &cnx_id) == 0) {
        ret = PICOQUIC_TRANSPORT_FRAME_FORMAT_ERROR;
    }

    if (ret == 0 && cnx->path[0]->remote_cnxid.id_len == 0) {
        /* Protocol error. The peer is using null length cnx_id */
        ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
    }

    /* Verify that the proposed CID is not already in use */
    for (int i = 0; ret == 0 && i < cnx->nb_paths; i++) {
        if (cnx->path[i]->remote_cnxid.id_len > 0) {
            if (sequence == cnx->path[i]->remote_cnxid_sequence) {
                if (picoquic_compare_connection_id(&cnx_id, &cnx->path[i]->remote_cnxid) == 0)
                {
                    if (memcmp(secret_bytes, cnx->path[i]->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
                        is_duplicate = 1;
                        break;
                    }
                    else {
                        DBG_PRINTF("Path %d, Cnx_id: %02x%02x%02x%02x..., Reset = %02x%02x%02x%02x... vs %02x%02x%02x%02x...\n",
                            i,
                            cnx->path[i]->remote_cnxid.id[0], cnx->path[i]->remote_cnxid.id[1],
                            cnx->path[i]->remote_cnxid.id[2], cnx->path[i]->remote_cnxid.id[3],
                            secret_bytes[0], secret_bytes[1], secret_bytes[2], secret_bytes[3],
                            cnx->path[i]->reset_secret[0], cnx->path[i]->reset_secret[1],
                            cnx->path[i]->reset_secret[2], cnx->path[i]->reset_secret[3]);
                        ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
                    }
                    break;
                }
                else {
                    DBG_PRINTF("Path %d, Sequence %d, Cnx_id: %02x%02x%02x%02x..., vs %02x%02x%02x%02x...\n",
                        i, (int)sequence,
                        cnx->path[i]->remote_cnxid.id[0], cnx->path[i]->remote_cnxid.id[1],
                        cnx->path[i]->remote_cnxid.id[2], cnx->path[i]->remote_cnxid.id[3],
                        cnx_id.id[0], cnx_id.id[1], cnx_id.id[2], cnx_id.id[3]);
                    ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
                }
            }
            else if (picoquic_compare_connection_id(&cnx_id, &cnx->path[i]->remote_cnxid) == 0) {
                DBG_PRINTF("Path %d, Cnx_id: %02x%02x%02x%02x..., Sequence %d vs. %d\n",
                    i, cnx_id.id[0], cnx_id.id[1], cnx_id.id[2], cnx_id.id[3],
                    (int)sequence, (int)cnx->path[i]->remote_cnxid_sequence);
                ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
            }
            else if (memcmp(secret_bytes, &cnx->path[i]->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
                DBG_PRINTF("Path %d, Cnx_id: %02x%02x%02x%02x..., Sequence %d vs. %d, same secret\n",
                    i, cnx_id.id[0], cnx_id.id[1], cnx_id.id[2], cnx_id.id[3],
                    (int)sequence, (int)cnx->path[i]->remote_cnxid_sequence);
                ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
            }
            else {
                if (!cnx->path[i]->path_is_demoted) {
                    uint64_t check = ((uint64_t)1) << cnx->path[i]->remote_cnxid_sequence;
                    if ((cnxid_mask & check) == 0) {
                        nb_cid_received++;
                        cnxid_mask |= check;
                    }
                }
            }
        }
    }
    
    while (ret == 0 && is_duplicate == 0 && next_stash != NULL) {
        if (picoquic_compare_connection_id(&cnx_id, &next_stash->cnx_id) == 0)
        {
            if (next_stash->sequence == sequence &&
                memcmp(secret_bytes, next_stash->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
                is_duplicate = 1;
            }
            else {
                ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
            }
            break;
        }
        else if (next_stash->sequence == sequence) {
            ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
        }
        else if (memcmp(secret_bytes, next_stash->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
            ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
        }
        else {
            nb_cid_received++;
        }
        last_stash = next_stash;
        next_stash = next_stash->next_in_stash;
    }

    if (ret == 0 && is_duplicate == 0) {
        if (nb_cid_received >= cnx->local_parameters.active_connection_id_limit) {
            ret = PICOQUIC_TRANSPORT_CONNECTION_ID_LIMIT_ERROR;
        }
        else {
            stashed = (picoquic_cnxid_stash_t*)malloc(sizeof(picoquic_cnxid_stash_t));

            if (stashed == NULL) {
                ret = PICOQUIC_TRANSPORT_INTERNAL_ERROR;
            }
            else {
                (void)picoquic_parse_connection_id(cnxid_bytes, cid_length, &stashed->cnx_id);
                stashed->sequence = sequence;
                memcpy(stashed->reset_secret, secret_bytes, PICOQUIC_RESET_SECRET_SIZE);
                stashed->next_in_stash = NULL;

                if (last_stash == NULL) {
                    cnx->cnxid_stash_first = stashed;
                }
                else {
                    last_stash->next_in_stash = stashed;
                }
            }
        }
    }

    /* the return argument is only used in tests */

    if (pstashed != NULL) {
        *pstashed = stashed;
    }

    return ret;
}

int picoquic_remove_not_before_cid(picoquic_cnx_t* cnx, uint64_t not_before, uint64_t current_time)
{
    int ret = 0;
    picoquic_cnxid_stash_t * next_stash = cnx->cnxid_stash_first;
    picoquic_cnxid_stash_t * previous_stash = NULL;

    while (ret == 0 && next_stash != NULL) {
        if (next_stash->sequence < not_before) {
            ret = picoquic_queue_retire_connection_id_frame(cnx, next_stash->sequence);
            if (ret == 0){
                next_stash = next_stash->next_in_stash;
                if (previous_stash == NULL) {
                    cnx->cnxid_stash_first = next_stash;
                }
                else {
                    previous_stash->next_in_stash = next_stash;
                }
            }
        }
        else {
            previous_stash = next_stash;
            next_stash = next_stash->next_in_stash;
        }
    }

    /* We need to stop transmitting data to the old CID. But we cannot just delete
     * the correspondng paths,because there may be some data in transit. We must
     * also ensure that at least one default path migrates successfully to a
     * valid CID. As long as new CID are available, we can simply replace the
     * old one by a new one. If no CID is available, the old path should be marked
     * as failing, and thus scheduled for deletion after a time-out */

    for (int i = 0; ret == 0 && i < cnx->nb_paths; i++) {
        if (cnx->path[i]->remote_cnxid_sequence < not_before &&
            cnx->path[i]->remote_cnxid.id_len > 0 && 
            !cnx->path[i]->path_is_demoted) {
            ret = picoquic_renew_connection_id(cnx, i);
            if (ret != 0) {
                DBG_PRINTF("Renew CNXID returns %x\n", ret);
                if (i == 0) {
                    ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
                }
                else {
                    ret = 0;
                    picoquic_demote_path(cnx, i, current_time);
                }
            }
        }
    }

    return ret;
}

/* Start using a new connection ID for the existing path
 */
int picoquic_renew_path_connection_id(picoquic_cnx_t* cnx, picoquic_path_t* path_x)
{
    int ret = 0;
    picoquic_cnxid_stash_t * stashed = NULL;

    if (cnx->remote_parameters.migration_disabled != 0 ||
        cnx->local_parameters.migration_disabled != 0) {
        /* Do not switch cnx_id if migration is disabled */
        ret = PICOQUIC_ERROR_MIGRATION_DISABLED;
    }
    else {
        stashed = picoquic_dequeue_cnxid_stash(cnx);

        if (stashed == NULL) {
            ret = PICOQUIC_ERROR_CNXID_NOT_AVAILABLE;
        } else {
            /* Count the references to the remote cnxid */
            int nb_cnxid_ref = 0;

            for (int i = 0; i < cnx->nb_paths; i++) {
                if (cnx->path[i]->remote_cnxid_sequence == path_x->remote_cnxid_sequence) {
                    nb_cnxid_ref++;
                }
            }

            if (nb_cnxid_ref <= 1) {
                /* if this was the last reference, retire the old cnxid */
                if (picoquic_queue_retire_connection_id_frame(cnx, path_x->remote_cnxid_sequence) != 0) {
                    DBG_PRINTF("Could not properly retire CID[%" PRIu64 "]", path_x->remote_cnxid_sequence);
                }
            }

            /* Install the new value */
            path_x->remote_cnxid = stashed->cnx_id;
            path_x->remote_cnxid_sequence = stashed->sequence;
            memcpy(path_x->reset_secret, stashed->reset_secret,
                PICOQUIC_RESET_SECRET_SIZE);
            free(stashed);

            /* If default path, reset the secret pointer */
            if (path_x == cnx->path[0]) {
                ret = picoquic_register_net_secret(cnx);
            }
        }
    }

    return ret;
}

int picoquic_renew_connection_id(picoquic_cnx_t* cnx, int path_id)
{
    int ret;

    if (path_id >= cnx->nb_paths) {
        ret = -1;
    }
    else {
        ret = picoquic_renew_path_connection_id(cnx, cnx->path[path_id]);
    }

    return ret;
}

/* stream data splay management */
int64_t picoquic_stream_data_node_compare(void* l, void* r)
{
    /* Offset values are from 0 to 2^62-1, which means we are not worried with rollover */
    return ((picoquic_stream_data_node_t*)l)->offset - ((picoquic_stream_data_node_t*)r)->offset;
}

picosplay_node_t* picoquic_stream_data_node_create(void* value)
{
    return &((picoquic_stream_data_node_t*)value)->stream_data_node;
}


void* picoquic_stream_data_node_value(picosplay_node_t* node)
{
    return (void*)((char*)node - offsetof(struct st_picoquic_stream_data_node_t, stream_data_node));
}


void picoquic_stream_data_node_delete(void* tree, picosplay_node_t* node)
{
    picoquic_stream_data_node_t* stream_data = (picoquic_stream_data_node_t*)picoquic_stream_data_node_value(node);

    if (stream_data->bytes != NULL) {
        free(stream_data->bytes);
        stream_data->bytes = NULL;
    }

    free(stream_data);
}

/* Stream splay management */

static int64_t picoquic_stream_node_compare(void *l, void *r)
{
    /* STream values are from 0 to 2^62-1, which means we are not worried with rollover */
    return ((picoquic_stream_head_t*)l)->stream_id - ((picoquic_stream_head_t*)r)->stream_id;
}

static picosplay_node_t * picoquic_stream_node_create(void * value)
{
    return &((picoquic_stream_head_t *)value)->stream_node;
}


static void * picoquic_stream_node_value(picosplay_node_t * node)
{
    return (void*)((char*)node - offsetof(struct st_picoquic_stream_head_t, stream_node));
}

void picoquic_clear_stream(picoquic_stream_head_t* stream)
{
    picoquic_stream_data_node_t* ready = stream->send_queue;
    picoquic_stream_data_node_t* next;

    while ((next = ready) != NULL) {
        ready = next->next_stream_data;

        if (next->bytes != NULL) {
            free(next->bytes);
        }
        free(next);
    }

    picosplay_empty_tree(&stream->stream_data_tree);

    while (stream->first_sack_item.next_sack != NULL) {
        picoquic_sack_item_t * sack = stream->first_sack_item.next_sack;
        stream->first_sack_item.next_sack = sack->next_sack;
        free(sack);
    }
}


static void picoquic_stream_node_delete(void * tree, picosplay_node_t * node)
{
    picoquic_stream_head_t * stream = picoquic_stream_node_value(node);

    picoquic_clear_stream(stream);

    free(stream);
}

/* Management of streams */

picoquic_stream_head_t * picoquic_stream_from_node(picosplay_node_t * node)
{
#ifdef TOO_CAUTIOUS
    return(picoquic_stream_head_t *)((node == NULL)?NULL:picoquic_stream_node_value(node));
#else
    return (picoquic_stream_head_t *)node;
#endif
}

picoquic_stream_head_t * picoquic_first_stream(picoquic_cnx_t* cnx)
{
#ifdef TOO_CAUTIOUS
    return picoquic_stream_from_node(picosplay_first(&cnx->stream_tree));
#else
    return (picoquic_stream_head_t *)picosplay_first(&cnx->stream_tree);
#endif
}

picoquic_stream_head_t * picoquic_last_stream(picoquic_cnx_t* cnx)
{
#ifdef TOO_CAUTIOUS
    return picoquic_stream_from_node(picosplay_last(&cnx->stream_tree));
#else
    return (picoquic_stream_head_t *)picosplay_last(&cnx->stream_tree);
#endif
}

void picoquic_insert_output_stream(picoquic_cnx_t* cnx, picoquic_stream_head_t * stream)
{
    if (stream->is_output_stream == 0) {
        if (stream->stream_id == cnx->high_priority_stream_id) {
            /* insert in front */
            stream->previous_output_stream = NULL;
            stream->next_output_stream = cnx->first_output_stream;
            if (cnx->first_output_stream != NULL) {
                cnx->first_output_stream->previous_output_stream = stream;
            }
            cnx->first_output_stream = stream;
        } else {
            stream->previous_output_stream = cnx->last_output_stream;
            stream->next_output_stream = NULL;
            if (cnx->last_output_stream == NULL) {
                cnx->first_output_stream = stream;
                cnx->last_output_stream = stream;
            }
            else {
                cnx->last_output_stream->next_output_stream = stream;
                cnx->last_output_stream = stream;
            }
        }
        stream->is_output_stream = 1;
    }
}

void picoquic_remove_output_stream(picoquic_cnx_t* cnx, picoquic_stream_head_t * stream, picoquic_stream_head_t * previous_stream)
{
    if (stream->is_output_stream) {
        stream->is_output_stream = 0;

        if (stream->previous_output_stream == NULL) {
            cnx->first_output_stream = stream->next_output_stream;
        }
        else {
            stream->previous_output_stream->next_output_stream = stream->next_output_stream;
        }

        if (stream->next_output_stream == NULL) {
            cnx->last_output_stream = stream->previous_output_stream;
        }
        else {
            stream->next_output_stream->previous_output_stream = stream->previous_output_stream;
        }
    }
}

picoquic_stream_head_t * picoquic_next_stream(picoquic_stream_head_t * stream)
{
    return (picoquic_stream_head_t *)picosplay_next((picosplay_node_t *)stream);
}

picoquic_stream_head_t* picoquic_find_stream(picoquic_cnx_t* cnx, uint64_t stream_id)
{
    picoquic_stream_head_t target;
    target.stream_id = stream_id;

    return (picoquic_stream_head_t *)picosplay_find(&cnx->stream_tree, (void*)&target);
}

void picoquic_add_output_streams(picoquic_cnx_t* cnx, uint64_t old_limit, uint64_t new_limit, unsigned int is_bidir)
{
    uint64_t old_rank = STREAM_RANK_FROM_ID(old_limit);
    uint64_t first_new_id = STREAM_ID_FROM_RANK(old_rank + 1ull, !cnx->client_mode, !is_bidir);
    picoquic_stream_head_t* stream = picoquic_find_stream(cnx, first_new_id );

    while (stream) {
        if (stream->stream_id > old_limit) {
            if (stream->stream_id > new_limit) {
                break;
            }
            if (IS_LOCAL_STREAM_ID(stream->stream_id, cnx->client_mode) && IS_BIDIR_STREAM_ID(stream->stream_id) == is_bidir) {
                picoquic_insert_output_stream(cnx, stream);
            }
        }
        stream = picoquic_next_stream(stream);
    }
}

picoquic_stream_head_t* picoquic_create_stream(picoquic_cnx_t* cnx, uint64_t stream_id)
{
    picoquic_stream_head_t* stream = (picoquic_stream_head_t*)malloc(sizeof(picoquic_stream_head_t));
    if (stream != NULL) {
        int is_output_stream = 0;
        memset(stream, 0, sizeof(picoquic_stream_head_t));
        stream->stream_id = stream_id;

        if (IS_LOCAL_STREAM_ID(stream_id, cnx->client_mode)) {
            if (IS_BIDIR_STREAM_ID(stream_id)) {
                stream->maxdata_local = cnx->local_parameters.initial_max_stream_data_bidi_local;
                stream->maxdata_remote = cnx->remote_parameters.initial_max_stream_data_bidi_remote;
                is_output_stream = stream->stream_id <= cnx->max_stream_id_bidir_remote;

            }
            else {
                stream->maxdata_local = 0;
                stream->maxdata_remote = cnx->remote_parameters.initial_max_stream_data_uni;
                is_output_stream = stream->stream_id <= cnx->max_stream_id_unidir_remote;
            }
        }
        else {
            if (IS_BIDIR_STREAM_ID(stream_id)) {
                stream->maxdata_local = cnx->local_parameters.initial_max_stream_data_bidi_remote;
                stream->maxdata_remote = cnx->remote_parameters.initial_max_stream_data_bidi_local;
                is_output_stream = 1;
            }
            else {
                stream->maxdata_local = cnx->local_parameters.initial_max_stream_data_uni;
                stream->maxdata_remote = 0;
                is_output_stream = 0;
            }
        }

        picosplay_init_tree(&stream->stream_data_tree, picoquic_stream_data_node_compare, picoquic_stream_data_node_create, picoquic_stream_data_node_delete, picoquic_stream_data_node_value);

        picosplay_insert(&cnx->stream_tree, stream);
        if (is_output_stream) {
            picoquic_insert_output_stream(cnx, stream);
        }
        else {
            picoquic_remove_output_stream(cnx, stream, NULL);
            picoquic_delete_stream_if_closed(cnx, stream);
        }

        if (stream_id >= cnx->next_stream_id[STREAM_TYPE_FROM_ID(stream_id)]) {
            cnx->next_stream_id[STREAM_TYPE_FROM_ID(stream_id)] = NEXT_STREAM_ID_FOR_TYPE(stream_id);
        }
    }

    return stream;
}

void picoquic_delete_stream(picoquic_cnx_t * cnx, picoquic_stream_head_t* stream)
{
    picosplay_delete(&cnx->stream_tree, stream);
}

int picoquic_mark_direct_receive_stream(picoquic_cnx_t* cnx, uint64_t stream_id, picoquic_stream_direct_receive_fn direct_receive_fn, void* direct_receive_ctx)
{
    int ret = 0;
    picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
    picoquic_stream_data_node_t* data;

    if (stream == NULL) {
        ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
    }
    else if (!IS_BIDIR_STREAM_ID(stream_id) && IS_LOCAL_STREAM_ID(stream_id, cnx->client_mode)) {
        ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
    }
    else if (direct_receive_fn == NULL) {
        /* This is illegal! */
        ret = PICOQUIC_ERROR_NO_CALLBACK_PROVIDED;
    }
    else {
        stream->direct_receive_fn = direct_receive_fn;
        stream->direct_receive_ctx = direct_receive_ctx;
        /* If there is pending data, pass it. */
        while ((data = (picoquic_stream_data_node_t*)picosplay_first(&stream->stream_data_tree)) != NULL) {
            size_t length = data->length;
            uint64_t offset = data->offset;
            uint8_t* bytes = data->bytes;

            if (offset < stream->consumed_offset) {
                if (offset + length < stream->consumed_offset) {
                    length = 0;
                }
                else {
                    size_t delta_offset = (size_t)(stream->consumed_offset - offset);
                    length -= delta_offset;
                    offset += delta_offset;
                }
            }

            if (length > 0) {
                ret = direct_receive_fn(cnx, stream_id, 0, bytes, offset, length, direct_receive_ctx);
            }

            if (ret == 0) {
                picosplay_delete_hint(&stream->stream_data_tree, &data->stream_data_node);
            }
            else {
                break;
            }
        }

        /* If there is a fin offset, pass it. */
        if (ret == 0 && stream->fin_received && !stream->fin_signalled) {
            uint8_t fin_bytes[8];
            ret = direct_receive_fn(cnx, stream_id, 1, fin_bytes, stream->fin_offset, 0, direct_receive_ctx);
        }
    }

    return ret;
}


/* Management of local CID.
 * Local CID are created and registered on demand.
 */

picoquic_local_cnxid_t* picoquic_create_local_cnxid(picoquic_cnx_t* cnx, picoquic_connection_id_t* suggested_value)
{
    picoquic_local_cnxid_t* l_cid = NULL;
    int is_unique = 0;

    l_cid = (picoquic_local_cnxid_t*)malloc(sizeof(picoquic_local_cnxid_t));

    if (l_cid != NULL) {
        memset(l_cid, 0, sizeof(picoquic_local_cnxid_t));
        if (cnx->quic->local_cnxid_length == 0) {
            is_unique = 1;
        }
        else {
            for (int i = 0; i < 32; i++) {
                if (i == 0 && suggested_value != NULL) {
                    l_cid->cnx_id = *suggested_value;
                }
                else {
                    picoquic_create_random_cnx_id(cnx->quic, &l_cid->cnx_id, cnx->quic->local_cnxid_length);

                    if (cnx->quic->cnx_id_callback_fn) {
                        cnx->quic->cnx_id_callback_fn(cnx->quic, l_cid->cnx_id, cnx->initial_cnxid,
                            cnx->quic->cnx_id_callback_ctx, &l_cid->cnx_id);
                    }
                }

                if (picoquic_cnx_by_id(cnx->quic, l_cid->cnx_id) == NULL) {
                    is_unique = 1;
                    break;
                }
            }
        }

        if (is_unique) {
            picoquic_local_cnxid_t* previous = NULL;
            picoquic_local_cnxid_t* next = cnx->local_cnxid_first;

            while (next != NULL) {
                previous = next;
                next = next->next;
            }

            if (previous == NULL) {
                cnx->local_cnxid_first = l_cid;
            }
            else {
                previous->next = l_cid;
            }

            l_cid->sequence = cnx->local_cnxid_sequence_next++;
            cnx->nb_local_cnxid++;

            if (cnx->quic->local_cnxid_length > 0) {
                picoquic_register_cnx_id(cnx->quic, cnx, l_cid);
            }
        }
        else {
            free(l_cid);
            l_cid = NULL;
        }
    }

    return l_cid;
}

void picoquic_delete_local_cnxid(picoquic_cnx_t* cnx, picoquic_local_cnxid_t* l_cid)
{
    picoquic_local_cnxid_t* previous = NULL;
    picoquic_local_cnxid_t* next = cnx->local_cnxid_first;


    /* Set l_cid references to NULL in path contexts */
    for (int i = 0; i < cnx->nb_paths; i++) {
        if (cnx->path[i]->p_local_cnxid == l_cid) {
            cnx->path[i]->p_local_cnxid = NULL;
        }
    }

    /* Remove from list */
    while (next != NULL) {
        if (next == l_cid) {
            if (previous == NULL) {
                cnx->local_cnxid_first = next->next;
            }
            else {
                previous->next = next->next;
            }
            cnx->nb_local_cnxid--;
            break;
        }
        else {
            previous = next;
            next = next->next;
        }
    }

    if (l_cid->cnx_id.id_len > 0) {
        /* Remove the registration in hash tables */
        if (l_cid->first_cnx_id != NULL) {
            picohash_item* item;
            picoquic_cnx_id_key_t* cnx_id_key = l_cid->first_cnx_id;

            item = picohash_retrieve(cnx->quic->table_cnx_by_id, cnx_id_key);
            if (item != NULL) {
                picohash_delete_item(cnx->quic->table_cnx_by_id, item, 1);
            }

            l_cid->first_cnx_id = NULL;
        }
    }

    /* Delete and done */
    free(l_cid);
}

void picoquic_retire_local_cnxid(picoquic_cnx_t* cnx, uint64_t sequence)
{
    picoquic_local_cnxid_t* local_cnxid = cnx->local_cnxid_first;

    while (local_cnxid != NULL) {
        if (local_cnxid->sequence == sequence) {
            break;
        }
        else {
            local_cnxid = local_cnxid->next;
        }
    }

    if (local_cnxid != NULL) {
        picoquic_delete_local_cnxid(cnx, local_cnxid);
    }
}

picoquic_local_cnxid_t* picoquic_find_local_cnxid(picoquic_cnx_t* cnx, picoquic_connection_id_t* cnxid)
{
    picoquic_local_cnxid_t* local_cnxid = cnx->local_cnxid_first;

    while (local_cnxid != NULL) {
        if (picoquic_compare_connection_id(&local_cnxid->cnx_id, cnxid) == 0) {
            break;
        }
        else {
            local_cnxid = local_cnxid->next;
        }
    }
    
    return local_cnxid;
}

/* Connection management
 */

picoquic_cnx_t* picoquic_create_cnx(picoquic_quic_t* quic,
    picoquic_connection_id_t initial_cnx_id, picoquic_connection_id_t remote_cnx_id, 
    const struct sockaddr* addr_to, uint64_t start_time, uint32_t preferred_version,
    char const* sni, char const* alpn, char client_mode)
{
    picoquic_cnx_t* cnx = (picoquic_cnx_t*)malloc(sizeof(picoquic_cnx_t));

    if (cnx != NULL) {
        int ret;
        picoquic_local_cnxid_t* cnxid0;

        memset(cnx, 0, sizeof(picoquic_cnx_t));
        cnx->start_time = start_time;
        cnx->client_mode = client_mode;
        if (client_mode) {
            if (picoquic_is_connection_id_null(&initial_cnx_id)) {
                picoquic_create_random_cnx_id(quic, &initial_cnx_id, 8);
            }
        }
        cnx->initial_cnxid = initial_cnx_id;
        cnx->quic = quic;
        /* Create the connection ID number 0 */
        cnxid0 = picoquic_create_local_cnxid(cnx, NULL);
        
        /* Should return 0, since this is the first path */
        ret = picoquic_create_path(cnx, start_time, NULL, addr_to);

        if (ret != 0 || cnxid0 == NULL) {
            free(cnx);
            cnx = NULL;
        } else {
            cnx->next_wake_time = start_time;
            SET_LAST_WAKE(quic, PICOQUIC_QUICCTX);
            picoquic_insert_cnx_in_list(quic, cnx);
            picoquic_insert_cnx_by_wake_time(quic, cnx);
            /* Do not require verification for default path */
            cnx->path[0]->p_local_cnxid = cnxid0;
            cnx->path[0]->challenge_verified = 1;

            cnx->high_priority_stream_id = (uint64_t)((int64_t)-1);
            for (int i = 0; i < 4; i++) {
                cnx->next_stream_id[i] = i;
            }
            picoquic_register_path(cnx, cnx->path[0]);
        }
    }

    if (cnx != NULL) {
        if (quic->default_tp == NULL) {
            picoquic_init_transport_parameters(&cnx->local_parameters, cnx->client_mode);
        } else {
            memcpy(&cnx->local_parameters, quic->default_tp, sizeof(picoquic_tp_t));
            /* If the default parameters include preferred address, document it */
            if (cnx->local_parameters.prefered_address.is_defined) {
                /* Create an additional CID */
                picoquic_local_cnxid_t* cnxid1 = picoquic_create_local_cnxid(cnx, NULL);
                if (cnxid1 != NULL){
                    /* copy the connection ID into the local parameter */
                    cnx->local_parameters.prefered_address.connection_id = cnxid1->cnx_id;
                    /* Create the reset secret */
                    (void)picoquic_create_cnxid_reset_secret(cnx->quic, &cnxid1->cnx_id,
                        cnx->local_parameters.prefered_address.statelessResetToken);
                }
            }
        }
        /* If local connection ID size is null, don't allow migration */
        if (quic->local_cnxid_length == 0) {
            cnx->local_parameters.migration_disabled = 1;
        }

        if (cnx->quic->mtu_max > 0)
        {
            cnx->local_parameters.max_packet_size = cnx->quic->mtu_max;
        }

        /* Initialize local flow control variables to advertised values */
        cnx->maxdata_local = ((uint64_t)cnx->local_parameters.initial_max_data);
        cnx->max_stream_id_bidir_local = cnx->local_parameters.initial_max_stream_id_bidir;
        cnx->max_stream_id_bidir_local_computed = STREAM_TYPE_FROM_ID(cnx->local_parameters.initial_max_stream_id_bidir);
        cnx->max_stream_id_unidir_local = cnx->local_parameters.initial_max_stream_id_unidir;
        cnx->max_stream_id_unidir_local_computed = STREAM_TYPE_FROM_ID(cnx->local_parameters.initial_max_stream_id_unidir);

        /* Initialize padding policy to default for context */
        cnx->padding_multiple = quic->padding_multiple_default;
        cnx->padding_minsize = quic->padding_minsize_default;

        /* Initialize spin policy, ensure that at least 1/8th of connections do not spin */
        cnx->spin_policy = quic->default_spin_policy;
        if (cnx->spin_policy == picoquic_spinbit_basic) {
            uint8_t rand256 = (uint8_t)picoquic_public_random_64();
            if (rand256 < PICOQUIC_SPIN_RESERVE_MOD_256) {
                cnx->spin_policy = picoquic_spinbit_null;
            }
        }
        else if (cnx->spin_policy == picoquic_spinbit_on) {
            /* Option used in test to avoid randomizing spin bit on/off */
            cnx->spin_policy = picoquic_spinbit_basic;
        }

        if (sni != NULL) {
            cnx->sni = picoquic_string_duplicate(sni);
        }

        if (alpn != NULL) {
            cnx->alpn = picoquic_string_duplicate(alpn);
        }

        cnx->callback_fn = quic->default_callback_fn;
        cnx->callback_ctx = quic->default_callback_ctx;
        cnx->congestion_alg = quic->default_congestion_alg;

        /* Initialize key rotation interval to default value */
        cnx->crypto_epoch_length_max = quic->crypto_epoch_length_max;

        /* Perform different initializations for clients and servers */
        if (cnx->client_mode) {
            if (preferred_version == 0) {
                cnx->proposed_version = picoquic_supported_versions[0].version;
                cnx->version_index = 0;
            } else {
                cnx->version_index = picoquic_get_version_index(preferred_version);
                if (cnx->version_index < 0) {
                    cnx->version_index = PICOQUIC_INTEROP_VERSION_INDEX;
                    if ((preferred_version & 0x0A0A0A0A) == 0x0A0A0A0A) {
                        /* This is a hack, to allow greasing the cnx ID */
                        cnx->proposed_version = preferred_version;

                    } else {
                        cnx->proposed_version = picoquic_supported_versions[PICOQUIC_INTEROP_VERSION_INDEX].version;
                    }
                } else {
                    cnx->proposed_version = preferred_version;
                }
            }

            cnx->cnx_state = picoquic_state_client_init;

            if (!quic->is_cert_store_not_empty || sni == NULL) {
                /* This is a hack. The open SSL certifier crashes if no name is specified,
                 * and always fails if no certificate is stored, so we just use a NULL verifier */
                picoquic_log_app_message(cnx, "%s -- certificate will not be verified.\n",
                    (sni == NULL) ? "No server name specified" : "No root crt list specified");

                picoquic_set_null_verifier(quic);
            }
        } else {
            cnx->is_half_open = 1;
            cnx->quic->current_number_half_open += 1;
            if (cnx->quic->current_number_half_open > cnx->quic->max_half_open_before_retry) {
                cnx->quic->check_token = 1;
            }
            for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
                cnx->tls_stream[epoch].send_queue = NULL;
            }
            cnx->cnx_state = picoquic_state_server_init;
            cnx->initial_cnxid = initial_cnx_id;
            cnx->path[0]->remote_cnxid = remote_cnx_id;

            cnx->version_index = picoquic_get_version_index(preferred_version);
            if (cnx->version_index < 0) {
                /* TODO: this is an internal error condition, should not happen */
                cnx->version_index = 0;
                cnx->proposed_version = picoquic_supported_versions[0].version;
            } else {
                cnx->proposed_version = preferred_version;
            }
        }

        for (picoquic_packet_context_enum pc = 0;
            pc < picoquic_nb_packet_context; pc++) {
            cnx->pkt_ctx[pc].first_sack_item.start_of_sack_range = (uint64_t)((int64_t)-1);
            cnx->pkt_ctx[pc].first_sack_item.end_of_sack_range = 0;
            cnx->pkt_ctx[pc].first_sack_item.next_sack = NULL;
            cnx->pkt_ctx[pc].highest_ack_sent = 0;
            cnx->pkt_ctx[pc].highest_ack_sent_time = start_time;
            cnx->pkt_ctx[pc].time_stamp_largest_received = (uint64_t)((int64_t)-1);
            if (quic->random_initial) {
                cnx->pkt_ctx[pc].send_sequence = picoquic_crypto_uniform_random(quic, PICOQUIC_PN_RANDOM_RANGE) +
                    PICOQUIC_PN_RANDOM_MIN;
            }
            else {
                cnx->pkt_ctx[pc].send_sequence = 0;
            }
            cnx->pkt_ctx[pc].nb_retransmit = 0;
            cnx->pkt_ctx[pc].latest_retransmit_time = 0;
            cnx->pkt_ctx[pc].retransmit_newest = NULL;
            cnx->pkt_ctx[pc].retransmit_oldest = NULL;
            cnx->pkt_ctx[pc].highest_acknowledged = cnx->pkt_ctx[pc].send_sequence - 1;
            cnx->pkt_ctx[pc].latest_time_acknowledged = start_time;
            cnx->pkt_ctx[pc].highest_acknowledged_time = start_time;
            cnx->pkt_ctx[pc].ack_needed = 0;
        }

        cnx->latest_progress_time = start_time;

        for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
            cnx->tls_stream[epoch].stream_id = 0;
            cnx->tls_stream[epoch].consumed_offset = 0;
            cnx->tls_stream[epoch].fin_offset = 0;
            cnx->tls_stream[epoch].stream_node.left = NULL;
            cnx->tls_stream[epoch].stream_node.parent = NULL;
            cnx->tls_stream[epoch].stream_node.right = NULL;
            cnx->tls_stream[epoch].sent_offset = 0;
            cnx->tls_stream[epoch].local_error = 0;
            cnx->tls_stream[epoch].remote_error = 0;
            cnx->tls_stream[epoch].maxdata_local = (uint64_t)((int64_t)-1);
            cnx->tls_stream[epoch].maxdata_remote = (uint64_t)((int64_t)-1);

            picosplay_init_tree(&cnx->tls_stream[epoch].stream_data_tree, picoquic_stream_data_node_compare, picoquic_stream_data_node_create, picoquic_stream_data_node_delete, picoquic_stream_data_node_value);

            /* No need to reset the state flags, as they are not used for the crypto stream */
        }
        
        cnx->ack_frequency_sequence_local = (uint64_t)((int64_t)-1);
        cnx->ack_gap_local = 2;
        cnx->ack_frequency_delay_local = PICOQUIC_ACK_DELAY_MAX_DEFAULT;
        cnx->ack_frequency_sequence_remote = (uint64_t)((int64_t)-1);
        cnx->ack_gap_remote = 2;
        cnx->ack_delay_remote = PICOQUIC_ACK_DELAY_MAX_DEFAULT;

        picosplay_init_tree(&cnx->stream_tree, picoquic_stream_node_compare, picoquic_stream_node_create, picoquic_stream_node_delete, picoquic_stream_node_value);

        cnx->congestion_alg = cnx->quic->default_congestion_alg;
        if (cnx->congestion_alg != NULL) {
            cnx->congestion_alg->alg_init(cnx->path[0], start_time);
        }
    }

    /* Only initialize TLS after all parameters have been set */
    if (cnx != NULL && picoquic_tlscontext_create(quic, cnx, start_time) != 0) {
        /* Cannot just do partial creation! */
        picoquic_delete_cnx(cnx);
        cnx = NULL;
    }

    if (cnx != NULL) {
        if (picoquic_setup_initial_traffic_keys(cnx)) {
            /* Cannot initialize aead for initial packets */
            picoquic_delete_cnx(cnx);
            cnx = NULL;
        }
    }

    if (cnx != NULL && !client_mode && quic->local_cnxid_length > 0) {
        if (picoquic_register_net_icid(cnx) != 0) {
            DBG_PRINTF("%s", "Could not register the ICID in table.\n");
            picoquic_delete_cnx(cnx);
            cnx = NULL;
        }
    }

    if (cnx != NULL) {
        picoquic_log_new_connection(cnx);
    }

    return cnx;
}

picoquic_cnx_t* picoquic_create_client_cnx(picoquic_quic_t* quic,
    struct sockaddr* addr, uint64_t start_time, uint32_t preferred_version,
    char const* sni, char const* alpn, picoquic_stream_data_cb_fn callback_fn, void* callback_ctx)
{
    picoquic_cnx_t* cnx = picoquic_create_cnx(quic, picoquic_null_connection_id, picoquic_null_connection_id, addr, start_time, preferred_version, sni, alpn, 1);

    if (cnx != NULL) {
        int ret;

        if (callback_fn != NULL)
            cnx->callback_fn = callback_fn;
        if (callback_ctx != NULL)
            cnx->callback_ctx = callback_ctx;
        ret = picoquic_start_client_cnx(cnx);
        if (ret != 0) {
            /* Cannot just do partial initialization! */
            picoquic_delete_cnx(cnx);
            cnx = NULL;
        }
    }

    return cnx;
}

int picoquic_start_client_cnx(picoquic_cnx_t * cnx)
{
    int ret = picoquic_initialize_tls_stream(cnx, picoquic_get_quic_time(cnx->quic));
    /* A remote session ticket may have been loaded as part of initializing TLS,
     * and remote parameters may have been initialized to the initial value
     * of the previous session. Apply these new parameters. */
    cnx->maxdata_remote = cnx->remote_parameters.initial_max_data;
    cnx->max_stream_id_bidir_remote = cnx->remote_parameters.initial_max_stream_id_bidir;
    cnx->max_stream_id_unidir_remote = cnx->remote_parameters.initial_max_stream_id_unidir;

    picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));

    return ret;
}

void picoquic_set_transport_parameters(picoquic_cnx_t * cnx, picoquic_tp_t const * tp)
{
    cnx->local_parameters = *tp;

    if (cnx->quic->mtu_max > 0)
    {
        cnx->local_parameters.max_packet_size = cnx->quic->mtu_max;
    }

    /* Initialize local flow control variables to advertised values */

    cnx->maxdata_local = ((uint64_t)cnx->local_parameters.initial_max_data);
    cnx->max_stream_id_bidir_local = cnx->local_parameters.initial_max_stream_id_bidir;
    cnx->max_stream_id_unidir_local = cnx->local_parameters.initial_max_stream_id_unidir;
}

picoquic_tp_t const* picoquic_get_transport_parameters(picoquic_cnx_t* cnx, int get_local)
{
    return(get_local) ? &cnx->local_parameters : &cnx->remote_parameters;
}

void picoquic_get_peer_addr(picoquic_cnx_t* cnx, struct sockaddr** addr)
{
    *addr = (struct sockaddr*)&cnx->path[0]->peer_addr;
}

void picoquic_get_local_addr(picoquic_cnx_t* cnx, struct sockaddr** addr)
{
    *addr = (struct sockaddr*)&cnx->path[0]->local_addr;
}

unsigned long picoquic_get_local_if_index(picoquic_cnx_t* cnx)
{
    return cnx->path[0]->if_index_dest;
}

picoquic_connection_id_t picoquic_get_local_cnxid(picoquic_cnx_t* cnx)
{
    return cnx->path[0]->p_local_cnxid->cnx_id;
}

picoquic_connection_id_t picoquic_get_remote_cnxid(picoquic_cnx_t* cnx)
{
    return cnx->path[0]->remote_cnxid;
}

picoquic_connection_id_t picoquic_get_initial_cnxid(picoquic_cnx_t* cnx)
{
    return cnx->initial_cnxid;
}

picoquic_connection_id_t picoquic_get_client_cnxid(picoquic_cnx_t* cnx)
{
    return (cnx->client_mode)?cnx->path[0]->p_local_cnxid->cnx_id : cnx->path[0]->remote_cnxid;
}

picoquic_connection_id_t picoquic_get_server_cnxid(picoquic_cnx_t* cnx)
{
    return (cnx->client_mode) ? cnx->path[0]->remote_cnxid : cnx->path[0]->p_local_cnxid->cnx_id;
}

picoquic_connection_id_t picoquic_get_logging_cnxid(picoquic_cnx_t* cnx)
{
    return cnx->initial_cnxid;
}

uint64_t picoquic_get_cnx_start_time(picoquic_cnx_t* cnx)
{
    return cnx->start_time;
}

picoquic_state_enum picoquic_get_cnx_state(picoquic_cnx_t* cnx)
{
    return cnx->cnx_state;
}

uint64_t picoquic_is_0rtt_available(picoquic_cnx_t* cnx)
{
    return (cnx->crypto_context[picoquic_epoch_0rtt].aead_encrypt == NULL) ? 0 : 1;
}

void picoquic_cnx_set_padding_policy(picoquic_cnx_t * cnx, uint32_t padding_multiple, uint32_t padding_minsize)
{
    cnx->padding_multiple = padding_multiple;
    cnx->padding_minsize = padding_minsize;
}

void picoquic_cnx_get_padding_policy(picoquic_cnx_t * cnx, uint32_t * padding_multiple, uint32_t * padding_minsize)
{
    *padding_multiple = cnx->padding_multiple;
    *padding_minsize = cnx->padding_minsize;
}

void picoquic_cnx_set_spinbit_policy(picoquic_cnx_t * cnx, picoquic_spinbit_version_enum spinbit_policy)
{
    cnx->spin_policy = spinbit_policy;
}

void picoquic_cnx_set_pmtud_required(picoquic_cnx_t* cnx, int is_pmtud_required)
{
    cnx->is_pmtud_required = is_pmtud_required;
}

/*
 * Provide clock time
 */
uint64_t picoquic_current_time()
{
    uint64_t now;
#ifdef _WINDOWS
    FILETIME ft;
    /*
    * The GetSystemTimeAsFileTime API returns  the number
    * of 100-nanosecond intervals since January 1, 1601 (UTC),
    * in FILETIME format.
    */
    GetSystemTimePreciseAsFileTime(&ft);

    /*
    * Convert to plain 64 bit format, without making
    * assumptions about the FILETIME structure alignment.
    */
    now = ft.dwHighDateTime;
    now <<= 32;
    now |= ft.dwLowDateTime;
    /*
    * Convert units from 100ns to 1us
    */
    now /= 10;
    /*
    * Account for microseconds elapsed between 1601 and 1970.
    */
    now -= 11644473600000000ULL;
#else
    struct timeval tv;
    (void)gettimeofday(&tv, NULL);
    now = (tv.tv_sec * 1000000ull) + tv.tv_usec;
#endif
    return now;
}

/*
* Get the same time simulation as used for TLS
*/

uint64_t picoquic_get_quic_time(picoquic_quic_t* quic)
{
    uint64_t now;
    if (quic->p_simulated_time == NULL) {
        now = picoquic_current_time();
    }
    else {
        now = *quic->p_simulated_time;
    }

    return now;
}

void picoquic_connection_id_callback(picoquic_quic_t * quic, picoquic_connection_id_t cnx_id_local, picoquic_connection_id_t cnx_id_remote, void * cnx_id_cb_data, picoquic_connection_id_t * cnx_id_returned)
{
    picoquic_connection_id_callback_ctx_t* ctx = (picoquic_connection_id_callback_ctx_t*)cnx_id_cb_data;

    quic->local_cnxid_length = ctx->cnx_id_val.id_len;

    /* Initialize with either random value or */
    memset(cnx_id_returned, 0, sizeof(picoquic_connection_id_t));
    if (ctx->cnx_id_select == picoquic_connection_id_remote) {
        /* Keeping this for compatibility with old buggy version */
        cnx_id_local = cnx_id_remote;
    } else {
        /* setting value to random data */
        picoquic_public_random(cnx_id_local.id, quic->local_cnxid_length);
    }
    cnx_id_local.id_len = quic->local_cnxid_length;

    /* Apply substitution under mask */
    for (uint8_t i = 0; i < cnx_id_local.id_len; i++) {
        cnx_id_returned->id[i] = (cnx_id_local.id[i] & ctx->cnx_id_mask.id[i]) | ctx->cnx_id_val.id[i];
    }
    cnx_id_returned->id_len = quic->local_cnxid_length;

    /* Apply encryption if required */
    switch (ctx->cnx_id_select) {
    case picoquic_connection_id_encrypt_basic:
        /* encryption under mask */
        if (ctx->cid_enc == NULL) {
            int ret = picoquic_cid_get_under_mask_ctx(&ctx->cid_enc, quic->reset_seed);
            if (ret != 0) {
                DBG_PRINTF("Cannot create CID encryption context, ret=%d\n", ret);
            }
        }
        if (ctx->cid_enc != NULL) {
            picoquic_cid_encrypt_under_mask(ctx->cid_enc, cnx_id_returned, &ctx->cnx_id_mask, cnx_id_returned);
        }
        break;
    default:
        /* Leave it unencrypted */
        break;
    }
}

picoquic_connection_id_callback_ctx_t * picoquic_connection_id_callback_create_ctx(
    char const * select_type, char const * default_value_hex, char const * mask_hex)
{
    picoquic_connection_id_callback_ctx_t* ctx = (picoquic_connection_id_callback_ctx_t*)
        malloc(sizeof(picoquic_connection_id_callback_ctx_t));

    if (ctx != NULL) {
        size_t lv, lm;
        memset(ctx, 0, sizeof(picoquic_connection_id_callback_ctx_t));
        ctx->cnx_id_select = atoi(select_type);
        /* TODO: find an alternative to parsing a 64 bit integer */
        lv = picoquic_parse_connection_id_hexa(default_value_hex, strlen(default_value_hex), &ctx->cnx_id_val);
        lm = picoquic_parse_connection_id_hexa(mask_hex, strlen(mask_hex), &ctx->cnx_id_val);

        if (lm == 0 || lv == 0 || lm != lv) {
            free(ctx);
            ctx = NULL;
        }
    }

    return ctx;
}

void picoquic_connection_id_callback_free_ctx(void * cnx_id_cb_data)
{
    picoquic_connection_id_callback_ctx_t* ctx = (picoquic_connection_id_callback_ctx_t*)cnx_id_cb_data;

    if (ctx != NULL && ctx->cid_enc != NULL) {
        switch (ctx->cnx_id_select) {
        case picoquic_connection_id_encrypt_basic:
            /* encryption under mask */
            picoquic_cid_free_under_mask_ctx(ctx->cid_enc);
            break;
        default:
            /* Guessing for the most common, assuming free will work... */
            picoquic_cid_free_under_mask_ctx(ctx->cid_enc);
            break;
        }
        ctx->cid_enc = NULL;
    }
    free(cnx_id_cb_data);
}

void picoquic_set_fuzz(picoquic_quic_t * quic, picoquic_fuzz_fn fuzz_fn, void * fuzz_ctx)
{
    quic->fuzz_fn = fuzz_fn;
    quic->fuzz_ctx = fuzz_ctx;
}

void picoquic_set_log_level(picoquic_quic_t* quic, int log_level)
{
    /* Only two level for now: log first 100 packets, or log everything. */
    quic->use_long_log = (log_level > 0) ? 1 : 0;
}

void picoquic_set_random_initial(picoquic_quic_t* quic, int random_initial)
{
    /* If set, triggers randomization of initial PN numbers. */
    quic->random_initial = (random_initial > 0) ? 1 : 0;
}

void picoquic_set_packet_train_mode(picoquic_quic_t* quic, int train_mode)
{
    /* TODO: consider setting high water mark for pacing. */
    /* If set, wait until pacing bucket is full enough to allow further transmissions. */
    quic->packet_train_mode = (train_mode > 0) ? 1 : 0;
}

void picoquic_set_padding_policy(picoquic_quic_t* quic, uint32_t padding_min_size, uint32_t padding_multiple)
{
    quic->padding_minsize_default = padding_min_size;
    quic->padding_multiple_default = padding_multiple;
}

int picoquic_set_default_connection_id_length(picoquic_quic_t* quic, uint8_t cid_length)
{
    int ret = 0;

    if (cid_length != quic->local_cnxid_length) {
        if (cid_length > PICOQUIC_CONNECTION_ID_MAX_SIZE) {
            ret = PICOQUIC_ERROR_CNXID_CHECK;
        }
        else if (quic->cnx_list != NULL) {
            ret = PICOQUIC_ERROR_CANNOT_CHANGE_ACTIVE_CONTEXT;
        }
        else {
            quic->local_cnxid_length = cid_length;
        }
    }

    return ret;
}

void picoquic_set_mtu_max(picoquic_quic_t* quic, uint32_t mtu_max)
{
    quic->mtu_max = mtu_max;
}

void picoquic_set_alpn_select_fn(picoquic_quic_t* quic, picoquic_alpn_select_fn alpn_select_fn)
{
    if (quic->default_alpn != NULL) {
        free((void *)quic->default_alpn);
        quic->default_alpn = NULL;
    }
    quic->alpn_select_fn = alpn_select_fn;
}

void picoquic_set_default_callback(picoquic_quic_t* quic,
    picoquic_stream_data_cb_fn callback_fn, void* callback_ctx)
{
    quic->default_callback_fn = callback_fn;
    quic->default_callback_ctx = callback_ctx;
}

void picoquic_set_callback(picoquic_cnx_t* cnx,
    picoquic_stream_data_cb_fn callback_fn, void* callback_ctx)
{
    cnx->callback_fn = callback_fn;
    cnx->callback_ctx = callback_ctx;
}

picoquic_stream_data_cb_fn picoquic_get_default_callback_function(picoquic_quic_t* quic)
{
    return quic->default_callback_fn;
}

void * picoquic_get_default_callback_context(picoquic_quic_t* quic)
{
    return quic->default_callback_ctx;
}

picoquic_stream_data_cb_fn picoquic_get_callback_function(picoquic_cnx_t * cnx)
{
    return cnx->callback_fn;
}

void * picoquic_get_callback_context(picoquic_cnx_t * cnx)
{
    return cnx->callback_ctx;
}

picoquic_misc_frame_header_t* picoquic_create_misc_frame(const uint8_t* bytes, size_t length, int is_pure_ack)
{
    size_t l_alloc = sizeof(picoquic_misc_frame_header_t) + length;

    if (l_alloc < sizeof(picoquic_misc_frame_header_t)) {
        return NULL;
    }
    else {
        picoquic_misc_frame_header_t* head = (picoquic_misc_frame_header_t*)malloc(l_alloc);
        if (head != NULL) {
            memset(head, 0, sizeof(picoquic_misc_frame_header_t));
            head->length = length;
            head->is_pure_ack = is_pure_ack;
            memcpy(((uint8_t *)head) + sizeof(picoquic_misc_frame_header_t), bytes, length);
        }
        return head;
    }
}

int picoquic_queue_misc_or_dg_frame(picoquic_cnx_t * cnx, picoquic_misc_frame_header_t** first, 
    picoquic_misc_frame_header_t** last, const uint8_t* bytes, size_t length, int is_pure_ack)
{
    int ret = 0;
    picoquic_misc_frame_header_t* misc_frame = picoquic_create_misc_frame(bytes, length, is_pure_ack);

    if (misc_frame == NULL) {
        ret = PICOQUIC_ERROR_MEMORY;
    } else {
        if (*last == NULL) {
            *first = misc_frame;
            *last = misc_frame;
        }
        else {
            (*last)->next_misc_frame = misc_frame;
            misc_frame->previous_misc_frame = *last;
            *last = misc_frame;
        }
    }

    picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));

    return ret;
}

int picoquic_queue_misc_frame(picoquic_cnx_t* cnx, const uint8_t* bytes, size_t length, int is_pure_ack)
{
    return picoquic_queue_misc_or_dg_frame(cnx, &cnx->first_misc_frame, &cnx->last_misc_frame, bytes, length, is_pure_ack);
}

void picoquic_delete_misc_or_dg(picoquic_misc_frame_header_t** first, picoquic_misc_frame_header_t** last, picoquic_misc_frame_header_t* frame)
{
    if (frame->next_misc_frame) {
        frame->next_misc_frame->previous_misc_frame = frame->previous_misc_frame;
    }
    else {
        *last = frame->previous_misc_frame;
    }

    if (frame->previous_misc_frame) {
        frame->previous_misc_frame->next_misc_frame = frame->next_misc_frame;
    }
    else {
        *first = frame->next_misc_frame;
    }

    free(frame);
}

void picoquic_reset_packet_context(picoquic_cnx_t* cnx,
    picoquic_packet_context_enum pc)
{
    /* TODO: special case for 0-RTT packets! */
    picoquic_packet_context_t * pkt_ctx = &cnx->pkt_ctx[pc];

    while (pkt_ctx->retransmit_newest != NULL) {
        (void)picoquic_dequeue_retransmit_packet(cnx, pkt_ctx->retransmit_newest, 1);
    }
    
    while (pkt_ctx->retransmitted_newest != NULL) {
        picoquic_dequeue_retransmitted_packet(cnx, pkt_ctx->retransmitted_newest);
    }

    pkt_ctx->retransmitted_oldest = NULL;

    while (pkt_ctx->first_sack_item.next_sack != NULL) {
        picoquic_sack_item_t * next = pkt_ctx->first_sack_item.next_sack;
        pkt_ctx->first_sack_item.next_sack = next->next_sack;
        free(next);
    }

    pkt_ctx->first_sack_item.start_of_sack_range = (uint64_t)((int64_t)-1);
    pkt_ctx->first_sack_item.end_of_sack_range = 0;
    /* Reset the ECN data */
    pkt_ctx->ecn_ect0_total_local = 0;
    pkt_ctx->ecn_ect1_total_local = 0;
    pkt_ctx->ecn_ce_total_local = 0;
    pkt_ctx->ecn_ect0_total_remote = 0;
    pkt_ctx->ecn_ect1_total_remote = 0;
    pkt_ctx->ecn_ce_total_remote = 0;
}

/*
* Reset the connection after an incoming retry packet.
*
* Can only happen after sending the client init packet.
* Result of reset:
*
* - connection ID is not changed.
* - sequence number is not changed.
* - all queued 0-RTT retransmission will be considered lost (to do with 0-RTT)
* - Client Initial packet is considered lost, free. A new one will have to be formatted.
* - TLS stream is reset, all TLS data is freed.
* - TLS API is called again.
* - State changes.
*/

int picoquic_reset_cnx(picoquic_cnx_t* cnx, uint64_t current_time)
{
    int ret = 0;

    /* Delete the packets queued for retransmission */
    for (picoquic_packet_context_enum pc = 0;
        pc < picoquic_nb_packet_context; pc++) {
        /* Do not reset the application context, in order to keep the 0-RTT
         * packets, and to keep using the same sequence number space in
         * the new connection */
        if (pc != picoquic_packet_context_application) {
            picoquic_reset_packet_context(cnx, pc);
        }
    }

    /* Reset the crypto stream */
    for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
        picoquic_clear_stream(&cnx->tls_stream[epoch]);
        cnx->tls_stream[epoch].consumed_offset = 0;
        cnx->tls_stream[epoch].fin_offset = 0;
        cnx->tls_stream[epoch].sent_offset = 0;
        /* No need to reset the state flags, are they are not used for the crypto stream */
    }

    for (int k = 0; k < 4; k++) {
        picoquic_crypto_context_free(&cnx->crypto_context[k]);
    }

    picoquic_crypto_context_free(&cnx->crypto_context_new);

    ret = picoquic_setup_initial_traffic_keys(cnx);

    /* Reset the TLS context, Re-initialize the tls connection */
    if (cnx->tls_ctx != NULL) {
        picoquic_tlscontext_free(cnx->tls_ctx);
        cnx->tls_ctx = NULL;
    }

    picoquic_log_new_connection(cnx);

    if (ret == 0) {
        ret = picoquic_tlscontext_create(cnx->quic, cnx, current_time);
    }
    if (ret == 0) {
        ret = picoquic_initialize_tls_stream(cnx, current_time);
    }

    return ret;
}

int picoquic_connection_error(picoquic_cnx_t* cnx, uint16_t local_error, uint64_t frame_type)
{
    if (cnx->cnx_state == picoquic_state_ready || 
        cnx->cnx_state == picoquic_state_client_ready_start || cnx->cnx_state == picoquic_state_server_false_start) {
        if (local_error > PICOQUIC_ERROR_CLASS) {
            cnx->local_error = PICOQUIC_TRANSPORT_INTERNAL_ERROR;
        }
        else {
            cnx->local_error = local_error;
        }
        cnx->cnx_state = picoquic_state_disconnecting;

        picoquic_log_app_message(cnx, "Protocol error 0x%x", local_error);
        DBG_PRINTF("Protocol error (%x)", local_error);
    } else if (cnx->cnx_state < picoquic_state_server_false_start) {
        if (cnx->cnx_state != picoquic_state_handshake_failure &&
            cnx->cnx_state != picoquic_state_handshake_failure_resend) {
            cnx->local_error = local_error;
            cnx->cnx_state = picoquic_state_handshake_failure;

            DBG_PRINTF("Protocol error %x", local_error);
        }
    }

    cnx->offending_frame_type = frame_type;

    return PICOQUIC_ERROR_DETECTED;
}

int picoquic_start_key_rotation(picoquic_cnx_t* cnx)
{
    int ret = 0;

    /* Verify that a packet of the previous rotation was acked */
    if (cnx->cnx_state != picoquic_state_ready ||
        cnx->crypto_epoch_sequence >
        cnx->pkt_ctx[picoquic_packet_context_application].first_sack_item.end_of_sack_range) {
        ret = PICOQUIC_ERROR_KEY_ROTATION_NOT_READY;
    }
    else {
        ret = picoquic_compute_new_rotated_keys(cnx);
    }

    if (ret == 0) {
        picoquic_apply_rotated_keys(cnx, 1);
        picoquic_crypto_context_free(&cnx->crypto_context_old);
        cnx->crypto_epoch_sequence = cnx->pkt_ctx[picoquic_packet_context_application].send_sequence;
    }

    return ret;
}

void picoquic_delete_sooner_packets(picoquic_cnx_t* cnx)
{
    picoquic_stateless_packet_t* packet = cnx->first_sooner;

    while (packet != NULL) {
        picoquic_stateless_packet_t* next_packet = packet->next_packet;
        picoquic_delete_stateless_packet(packet);
        packet = next_packet;
    }
    cnx->first_sooner = NULL;
}

void picoquic_delete_cnx(picoquic_cnx_t* cnx)
{
    picoquic_cnxid_stash_t* stashed_cnxid;

    if (cnx != NULL) {
        picoquic_log_close_connection(cnx);

        if (cnx->is_half_open && cnx->quic->current_number_half_open > 0) {
            cnx->quic->current_number_half_open--;
            cnx->is_half_open = 0;
        }

        if (cnx->cnx_state < picoquic_state_disconnected) {
            /* Give the application a chance to clean up its state */
            cnx->cnx_state = picoquic_state_disconnected;
            if (cnx->callback_fn) {
                (void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
            }
        }

        if (cnx->alpn != NULL) {
            free((void*)cnx->alpn);
            cnx->alpn = NULL;
        }

        if (cnx->sni != NULL) {
            free((void*)cnx->sni);
            cnx->sni = NULL;
        }

        if (cnx->retry_token != NULL) {
            free(cnx->retry_token);
            cnx->retry_token = NULL;
        }

        picoquic_delete_sooner_packets(cnx);

        picoquic_remove_cnx_from_list(cnx);
        picoquic_remove_cnx_from_wake_list(cnx);

        for (int i = 0; i < 4; i++) {
            picoquic_crypto_context_free(&cnx->crypto_context[i]);
        }

        picoquic_crypto_context_free(&cnx->crypto_context_new);

        for (picoquic_packet_context_enum pc = 0;
            pc < picoquic_nb_packet_context; pc++) {
            picoquic_reset_packet_context(cnx, pc);
        }

        while (cnx->first_misc_frame != NULL) {
            picoquic_delete_misc_or_dg(&cnx->first_misc_frame, &cnx->last_misc_frame, cnx->first_misc_frame);
        }

        while (cnx->first_datagram != NULL) {
            picoquic_delete_misc_or_dg(&cnx->first_datagram, &cnx->last_datagram, cnx->first_datagram);
        }

        for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
            picoquic_clear_stream(&cnx->tls_stream[epoch]);
        }

        picosplay_empty_tree(&cnx->stream_tree);

        if (cnx->tls_ctx != NULL) {
            picoquic_tlscontext_free(cnx->tls_ctx);
            cnx->tls_ctx = NULL;
        }

        if (cnx->path != NULL)
        {
            while (cnx->nb_paths > 0) {
                picoquic_delete_path(cnx, cnx->nb_paths - 1);
            }

            free(cnx->path);
            cnx->path = NULL;
        }

        while (cnx->local_cnxid_first != NULL) {
            picoquic_delete_local_cnxid(cnx, cnx->local_cnxid_first);
        }

        while ((stashed_cnxid = picoquic_dequeue_cnxid_stash(cnx)) != NULL) {
            free(stashed_cnxid);
        }

        free(cnx);
    }
}

int picoquic_is_handshake_error(uint16_t error_code)
{
    return ((error_code & 0xFF00) == PICOQUIC_TRANSPORT_CRYPTO_ERROR(0) ||
        error_code == PICOQUIC_TLS_HANDSHAKE_FAILED);
}

/* Context retrieval functions */
picoquic_cnx_t* picoquic_cnx_by_id(picoquic_quic_t* quic, picoquic_connection_id_t cnx_id)
{
    picoquic_cnx_t* ret = NULL;
    picohash_item* item;
    picoquic_cnx_id_key_t key;

    memset(&key, 0, sizeof(key));
    key.cnx_id = cnx_id;

    item = picohash_retrieve(quic->table_cnx_by_id, &key);

    if (item != NULL) {
        ret = ((picoquic_cnx_id_key_t*)item->key)->cnx;
    }
    return ret;
}

picoquic_cnx_t* picoquic_cnx_by_net(picoquic_quic_t* quic, const struct sockaddr* addr)
{
    picoquic_cnx_t* ret = NULL;
    picohash_item* item;
    picoquic_net_id_key_t key;

    memset(&key, 0, sizeof(key));
    picoquic_store_addr(&key.saddr, addr);

    item = picohash_retrieve(quic->table_cnx_by_net, &key);

    if (item != NULL) {
        ret = ((picoquic_net_id_key_t*)item->key)->cnx;
    }
    return ret;
}

picoquic_cnx_t* picoquic_cnx_by_icid(picoquic_quic_t* quic, picoquic_connection_id_t* icid,
    const struct sockaddr* addr)
{
    picoquic_cnx_t* ret = NULL;
    picohash_item* item;
    picoquic_net_icid_key_t key;

    memset(&key, 0, sizeof(key));
    picoquic_store_addr(&key.saddr, addr);
    key.icid = *icid;

    item = picohash_retrieve(quic->table_cnx_by_icid, &key);

    if (item != NULL) {
        ret = ((picoquic_net_icid_key_t*)item->key)->cnx;
    }
    return ret;
}

picoquic_cnx_t* picoquic_cnx_by_secret(picoquic_quic_t* quic, const uint8_t* reset_secret, const struct sockaddr* addr)
{
    picoquic_cnx_t* ret = NULL;
    picohash_item* item;
    picoquic_net_secret_key_t key;

    memset(&key, 0, sizeof(key));
    picoquic_store_addr(&key.saddr, addr);
    memcpy(key.reset_secret, reset_secret, PICOQUIC_RESET_SECRET_SIZE);

    item = picohash_retrieve(quic->table_cnx_by_secret, &key);

    if (item != NULL) {
        ret = ((picoquic_net_secret_key_t*)item->key)->cnx;
    }
    return ret;
}

/* Get congestion control algorithm by name */
picoquic_congestion_algorithm_t const* picoquic_get_congestion_algorithm(char const* alg_name)
{
    picoquic_congestion_algorithm_t const* alg = NULL;
    if (alg_name != NULL) {
        if (strcmp(alg_name, "reno") == 0) {
            alg = picoquic_newreno_algorithm;
        }
        else if (strcmp(alg_name, "cubic") == 0) {
            alg = picoquic_cubic_algorithm;
        }
        else if (strcmp(alg_name, "dcubic") == 0) {
            alg = picoquic_dcubic_algorithm;
        }
        else if (strcmp(alg_name, "fast") == 0) {
            alg = picoquic_fastcc_algorithm;
        }
        else if (strcmp(alg_name, "bbr") == 0) {
            alg = picoquic_bbr_algorithm;
        }
        else {
            alg = NULL;
        }
    }
    return alg;
}
/*
 * Set or reset the congestion control algorithm
 */

void picoquic_set_default_congestion_algorithm(picoquic_quic_t* quic, picoquic_congestion_algorithm_t const* alg)
{
    quic->default_congestion_alg = alg;
}

void picoquic_set_default_congestion_algorithm_by_name(picoquic_quic_t* quic, char const * alg_name)
{
    quic->default_congestion_alg = picoquic_get_congestion_algorithm(alg_name);
}

/*
 * Set the optimistic ack policy
 */

void picoquic_set_optimistic_ack_policy(picoquic_quic_t* quic, uint32_t sequence_hole_pseudo_period)
{
    quic->sequence_hole_pseudo_period = sequence_hole_pseudo_period;
}

void picoquic_set_congestion_algorithm(picoquic_cnx_t* cnx, picoquic_congestion_algorithm_t const* alg)
{
    if (cnx->congestion_alg != NULL) {
        if (cnx->path != NULL) {
            for (int i = 0; i < cnx->nb_paths; i++) {
                cnx->congestion_alg->alg_delete(cnx->path[i]);
            }
        }
    }

    cnx->congestion_alg = alg;

    if (cnx->congestion_alg != NULL) {
        if (cnx->path != NULL) {
            for (int i = 0; i < cnx->nb_paths; i++) {
                cnx->congestion_alg->alg_init(cnx->path[i], picoquic_get_quic_time(cnx->quic));
            }
        }
    }
}

void picoquic_subscribe_pacing_rate_updates(picoquic_cnx_t* cnx, uint64_t decrease_threshold, uint64_t increase_threshold)
{
    cnx->pacing_decrease_threshold = decrease_threshold;
    cnx->pacing_increase_threshold = increase_threshold;
    cnx->is_pacing_update_requested = (decrease_threshold != UINT64_MAX || increase_threshold != UINT64_MAX);
}

uint64_t picoquic_get_pacing_rate(picoquic_cnx_t* cnx)
{
    return cnx->path[0]->pacing_rate;
}

uint64_t picoquic_get_cwin(picoquic_cnx_t* cnx)
{
    return cnx->path[0]->cwin;
}

uint64_t picoquic_get_rtt(picoquic_cnx_t* cnx)
{
    return cnx->path[0]->smoothed_rtt;
}

int picoquic_set_local_addr(picoquic_cnx_t* cnx, struct sockaddr* addr)
{
    int ret = 0;

    if (cnx != NULL && cnx->path[0] != NULL && cnx->path[0]->local_addr.ss_family == 0) {
        picoquic_store_addr(&cnx->path[0]->local_addr, addr);
        ret = (cnx->path[0]->local_addr.ss_family == 0) ? -1 : 0;
    }
    else {
        ret = -1;
    }

    return ret;
}

void picoquic_enable_keep_alive(picoquic_cnx_t* cnx, uint64_t interval)
{
    if (interval == 0) {
        /* Use the negotiated value */
        uint64_t idle_timeout = cnx->idle_timeout;
        if (idle_timeout == 0) {
            /* Idle timeout is only initialized after parameters are negotiated  */
            idle_timeout = cnx->local_parameters.idle_timeout * 1000ull;
        }
        /* Ensure at least 3 PTO*/
        if (idle_timeout < 3 * cnx->path[0]->retransmit_timer) {
            idle_timeout = 3 * cnx->path[0]->retransmit_timer;
        }
        /* set interval to half that value */
        cnx->keep_alive_interval = idle_timeout / 2;
    } else {
        cnx->keep_alive_interval = interval;
    }
}

void picoquic_disable_keep_alive(picoquic_cnx_t* cnx)
{
    cnx->keep_alive_interval = 0;
}

int picoquic_set_verify_certificate_callback(picoquic_quic_t* quic, picoquic_verify_certificate_cb_fn cb, void* ctx,
                                             picoquic_free_verify_certificate_ctx free_fn) {
    picoquic_dispose_verify_certificate_callback(quic, quic->verify_certificate_callback_fn != NULL);

    quic->verify_certificate_callback_fn = cb;
    quic->free_verify_certificate_callback_fn = free_fn;
    quic->verify_certificate_ctx = ctx;

    return picoquic_enable_custom_verify_certificate_callback(quic);
}

int picoquic_is_client(picoquic_cnx_t* cnx)
{
    return cnx->client_mode;
}

int picoquic_get_local_error(picoquic_cnx_t* cnx)
{
    return cnx->local_error;
}

int picoquic_get_remote_error(picoquic_cnx_t* cnx)
{
    return cnx->remote_error;
}

uint64_t picoquic_get_remote_stream_error(picoquic_cnx_t* cnx, uint64_t stream_id)
{
    uint64_t remote_error = 0;
    picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
    if (stream != NULL) {
        remote_error = stream->remote_error;
    }
    return remote_error;
}

uint64_t picoquic_get_data_sent(picoquic_cnx_t* cnx)
{
    return cnx->data_sent;
}

uint64_t picoquic_get_data_received(picoquic_cnx_t* cnx)
{
    return cnx->data_received;
}

void picoquic_set_client_authentication(picoquic_quic_t* quic, int client_authentication) {
    picoquic_tls_set_client_authentication(quic, client_authentication);
}

/* Load balancer support is defined in https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/
 * The draft defines methods for encoding a server ID in a connection identifier, and optionally
 * obfuscating or encrypting the CID value. The CID are generated by the individual servers,
 * based on configuration options provided by the load balancer. The draft also defines
 * methods for generating retry tokens either by a protection box colocated with the
 * load balancer, or at the individual server, with methods for letting individual
 * servers retrieve information from the tokens.
 */

static void picoquic_lb_compat_cid_generate_clear(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t * lb_ctx, picoquic_connection_id_t* cnx_id_returned)
{
    cnx_id_returned->id[0] = lb_ctx->first_byte;
    memcpy(cnx_id_returned->id + 1, lb_ctx->server_id, lb_ctx->server_id_length);
}

static void picoquic_lb_compat_cid_generate_obfuscated(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t* cnx_id_returned)
{
    uint64_t obfuscation_max = (UINT64_MAX - lb_ctx->server_id64) / lb_ctx->divider;
    uint64_t obfuscator = 0;
    uint64_t obfuscated;

    cnx_id_returned->id[0] = lb_ctx->first_byte;
    for (size_t i = 0; i < lb_ctx->routing_bits_length; i++) {
        obfuscator <<= 8;
        obfuscator += cnx_id_returned->id[i + 1];
        obfuscator %= obfuscation_max;
    }
    obfuscated = obfuscator* lb_ctx->divider;
    obfuscated += lb_ctx->server_id64;

    for (size_t i = 0; i < lb_ctx->routing_bits_length; i++) {
        size_t j = lb_ctx->routing_bits_length - i; /* varies from lb_ctx->routing_bits_length to 1 */
        cnx_id_returned->id[j] = (uint8_t)obfuscated;
        obfuscated >>= 8;
    }
}

static void picoquic_lb_compat_cid_one_pass_stream(void * enc_ctx, uint8_t * nonce, size_t nonce_length, uint8_t * target, size_t target_length)
{
    uint8_t mask[16];
    /* Set the obfuscation value */
    memset(mask, 0, sizeof(mask));
    memcpy(mask, nonce, nonce_length);
    /* Encrypt with ECB */
    picoquic_aes128_ecb_encrypt(enc_ctx, mask, mask, sizeof(mask));
    /* Apply the mask */
    for (size_t i = 0; i < target_length; i++) {
        target[i] ^= mask[i];
    }
}

static void picoquic_lb_compat_cid_generate_stream_cipher(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t* cnx_id_returned)
{
    size_t id_offset = ((size_t)1) + lb_ctx->nonce_length;
    /* Prepare a clear text server ID */
    cnx_id_returned->id[0] = lb_ctx->first_byte;
    memcpy(cnx_id_returned->id + id_offset, lb_ctx->server_id, lb_ctx->server_id_length);
    /* First pass -- obtain intermediate server ID */
    picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, cnx_id_returned->id + 1, lb_ctx->nonce_length,
        cnx_id_returned->id + id_offset, lb_ctx->server_id_length);
    /* Second pass -- obtain encrypted nonce */
    picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, 
        cnx_id_returned->id + id_offset, lb_ctx->server_id_length,
        cnx_id_returned->id + 1, lb_ctx->nonce_length);
    /* Third pass -- obtain encrypted server-id */
    picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, cnx_id_returned->id + 1, lb_ctx->nonce_length,
        cnx_id_returned->id + id_offset, lb_ctx->server_id_length);
}

static void picoquic_lb_compat_cid_generate_block_cipher(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t* cnx_id_returned)
{
    cnx_id_returned->id[0] = lb_ctx->first_byte;
    /* Copy the server ID */
    memcpy(cnx_id_returned->id + 1, lb_ctx->server_id, lb_ctx->server_id_length);
    /* Set the zeropad value */
    memset(cnx_id_returned->id + 1 + lb_ctx->server_id_length, 0, lb_ctx->zero_pad_length);
    /* encrypt 16 bytes */
    picoquic_aes128_ecb_encrypt(lb_ctx->cid_encryption_context, cnx_id_returned->id + 1, cnx_id_returned->id + 1, 16);
    cnx_id_returned->id[0] = lb_ctx->first_byte;
}

void picoquic_lb_compat_cid_generate(picoquic_quic_t* quic, picoquic_connection_id_t cnx_id_local,
    picoquic_connection_id_t cnx_id_remote, void* cnx_id_cb_data, picoquic_connection_id_t* cnx_id_returned)
{
    picoquic_load_balancer_cid_context_t* lb_ctx = (picoquic_load_balancer_cid_context_t*)cnx_id_cb_data;
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(cnx_id_local);
    UNREFERENCED_PARAMETER(cnx_id_remote);
#endif
    switch (lb_ctx->method) {
    case picoquic_load_balancer_cid_clear:
        picoquic_lb_compat_cid_generate_clear(quic, lb_ctx, cnx_id_returned);
        break;
    case picoquic_load_balancer_cid_obfuscated:
        picoquic_lb_compat_cid_generate_obfuscated(quic, lb_ctx, cnx_id_returned);
        break;
    case picoquic_load_balancer_cid_stream_cipher:
        picoquic_lb_compat_cid_generate_stream_cipher(quic, lb_ctx, cnx_id_returned);
        break;
    case picoquic_load_balancer_cid_block_cipher:
        picoquic_lb_compat_cid_generate_block_cipher(quic, lb_ctx, cnx_id_returned);
        break;
    default:
        /* Error, unknown method */
        break;
    }
}

static uint64_t picoquic_lb_compat_cid_verify_clear(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
{
    uint64_t s_id64 = 0;

    for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
        s_id64 <<= 8;
        s_id64 += cnx_id->id[i + 1];
    }

    return s_id64;
}

static uint64_t picoquic_lb_compat_cid_verify_obfuscated(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
{
    uint64_t s_id64 = 0;

    for (size_t i = 0; i < lb_ctx->routing_bits_length; i++) {
        s_id64 <<= 8;
        s_id64 += cnx_id->id[i + 1];
        s_id64 %= lb_ctx->divider;
    }

    return s_id64;
}

static uint64_t picoquic_lb_compat_cid_verify_stream_cipher(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
{
    size_t id_offset = ((size_t)1) + lb_ctx->nonce_length;
    uint64_t s_id64 = 0;
    picoquic_connection_id_t target = *cnx_id;
    /* First pass -- obtain intermediate server ID */
    picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, target.id + 1, lb_ctx->nonce_length,
        target.id + id_offset, lb_ctx->server_id_length);
    /* Second pass -- obtain nonce */
    picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context,
        target.id + id_offset, lb_ctx->server_id_length, target.id + 1, lb_ctx->nonce_length);
    /* First pass -- obtain server-id */
    picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, target.id + 1, lb_ctx->nonce_length,
        target.id + id_offset, lb_ctx->server_id_length);

    /* decode the server ID */
    for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
        s_id64 <<= 8;
        s_id64 += target.id[id_offset + i];
    }

    return s_id64;
}

static uint64_t picoquic_lb_compat_cid_verify_block_cipher(picoquic_quic_t* quic,
    picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
{
    uint8_t decoded[16];
    uint64_t s_id64 = 0;

    /* decrypt 16 bytes */
    picoquic_aes128_ecb_encrypt(lb_ctx->cid_decryption_context, decoded, cnx_id->id + 1, 16);

    /* Check that the nonce is all zeros */
    for (size_t i = 0; i < lb_ctx->zero_pad_length; i++) {
        if (decoded[i + lb_ctx->server_id_length] != 0) {
            s_id64 = UINT64_MAX;
            break;
        }
    }
    /* Decode the server ID */
    if (s_id64 == 0) {
        for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
            s_id64 <<= 8;
            s_id64 += decoded[i];
        }
    }

    return s_id64;
}

uint64_t picoquic_lb_compat_cid_verify(picoquic_quic_t* quic, void* cnx_id_cb_data, picoquic_connection_id_t const* cnx_id)
{
    picoquic_load_balancer_cid_context_t* lb_ctx = (picoquic_load_balancer_cid_context_t*)cnx_id_cb_data;
    uint64_t server_id64;

    if (cnx_id->id_len != lb_ctx->connection_id_length) {
        server_id64 = UINT64_MAX;
    }
    else {
        switch (lb_ctx->method) {
        case picoquic_load_balancer_cid_clear:
            server_id64 = picoquic_lb_compat_cid_verify_clear(quic, lb_ctx, cnx_id);
            break;
        case picoquic_load_balancer_cid_obfuscated:
            server_id64 = picoquic_lb_compat_cid_verify_obfuscated(quic, lb_ctx, cnx_id);
            break;
        case picoquic_load_balancer_cid_stream_cipher:
            server_id64 = picoquic_lb_compat_cid_verify_stream_cipher(quic, lb_ctx, cnx_id);
            break;
        case picoquic_load_balancer_cid_block_cipher:
            server_id64 = picoquic_lb_compat_cid_verify_block_cipher(quic, lb_ctx, cnx_id);
            break;
        default:
            /* Error, unknown method */
            server_id64 = UINT64_MAX;
            break;
        }
    }

    return server_id64;
}

int picoquic_lb_compat_cid_config(picoquic_quic_t* quic, picoquic_load_balancer_config_t * lb_config)
{
    int ret = 0;

    if (quic->cnx_list != NULL && quic->local_cnxid_length != lb_config->connection_id_length) {
        /* Error. Changing the CID length now will break existing connections */
        ret = -1;
    }
    else if (quic->cnx_id_callback_fn != NULL && quic->cnx_id_callback_ctx != NULL){
        /* Error. Some other CID generation is configured, cannot be changed */
        ret = -1;
    }
    else {
        /* Verify that the method is supported and the parameters are compatible.
         * If valid, configure the connection ID generation */
        if (lb_config->connection_id_length > PICOQUIC_CONNECTION_ID_MAX_SIZE) {
            ret = -1;
        }
        else {
            switch (lb_config->method) {
            case picoquic_load_balancer_cid_clear:
                /* Require at least 2 bytes to make the CID unique */
                if (lb_config->server_id_length + 1 + 2 > lb_config->connection_id_length) {
                    ret = -1;
                }
                break;
            case picoquic_load_balancer_cid_obfuscated:
                /* Require at least 2 bytes to obfuscate the server CID,
                 * cannot handle undivided values larger than 8 bytes */
                if (lb_config->routing_bits_length + 1 > lb_config->connection_id_length ||
                    lb_config->server_id_length + 2 > lb_config->routing_bits_length ||
                    lb_config->routing_bits_length > 8 ||
                    lb_config->divider == 0) {
                    ret = -1;
                }
                break;
            case picoquic_load_balancer_cid_stream_cipher:
                /* Nonce length must be 8 to 16 bytes, CID should be long enough */
                if (lb_config->nonce_length < 8 || lb_config->nonce_length > 16 ||
                    lb_config->nonce_length + lb_config->server_id_length + 1 > lb_config->connection_id_length) {
                    ret = -1;
                }
                break;
            case picoquic_load_balancer_cid_block_cipher:
                /* CID should include a whole AES-ECB block,
                 * there should be at least 2 bytes available for uniqueness,
                 * zero padding length should be 4 bytes for security */
                if (lb_config->connection_id_length < 17 ||
                    lb_config->server_id_length + lb_config->zero_pad_length + 1 + 2 > lb_config->connection_id_length ||
                    lb_config->zero_pad_length < 4 ) {
                    ret = -1;
                }
                break;
            default:
                /* Error, unknown method */
                ret = -1;
                break;
            }
        }
        if (ret == 0) {
            /* Create a copy */
            picoquic_load_balancer_cid_context_t* lb_ctx = (picoquic_load_balancer_cid_context_t*)malloc(sizeof(picoquic_load_balancer_cid_context_t));

            if (lb_ctx == NULL) {
                ret = -1;
            }
            else {
                /* if allocated, create the necessary encryption contexts or variables */
                uint64_t s_id64 = lb_config->server_id64;
                memset(lb_ctx, 0, sizeof(picoquic_load_balancer_cid_context_t));
                lb_ctx->method = lb_config->method;
                lb_ctx->server_id_length = lb_config->server_id_length;
                lb_ctx->routing_bits_length = lb_config->routing_bits_length;
                lb_ctx->nonce_length = lb_config->nonce_length;
                lb_ctx->zero_pad_length = lb_config->zero_pad_length;
                lb_ctx->connection_id_length = lb_config->connection_id_length;
                lb_ctx->first_byte = lb_config->first_byte;
                lb_ctx->server_id64 = lb_config->server_id64;
                lb_ctx->divider = lb_config->divider;
                lb_ctx->cid_encryption_context = NULL;
                lb_ctx->cid_decryption_context = NULL;
                /* Compute the server ID bytes and set encryption contexts */
                for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
                    size_t j = lb_ctx->server_id_length - i - 1;
                    lb_ctx->server_id[j] = (uint8_t)s_id64;
                    s_id64 >>= 8;
                }
                if (s_id64 != 0) {
                    /* Server ID not long enough to encode actual value */
                    ret = -1;
                } else if (lb_config->method == picoquic_load_balancer_cid_stream_cipher ||
                    lb_config->method == picoquic_load_balancer_cid_block_cipher) {
                    lb_ctx->cid_encryption_context = picoquic_aes128_ecb_create(1, lb_config->cid_encryption_key);
                    if (lb_ctx->cid_encryption_context == NULL) {
                        ret = -1;
                    }
                    else if (lb_config->method == picoquic_load_balancer_cid_block_cipher) {
                        lb_ctx->cid_decryption_context = picoquic_aes128_ecb_create(0, lb_config->cid_encryption_key);
                        if (lb_ctx->cid_decryption_context == NULL) {
                            picoquic_aes128_ecb_free(lb_ctx->cid_encryption_context);
                            lb_ctx->cid_encryption_context = NULL;
                            ret = -1;
                        }
                    }
                }
                if (ret != 0) {
                    /* if context allocation failed, free the copy */
                    free(lb_ctx);
                    lb_ctx = NULL;
                } else {
                    /* Configure the CID generation */
                    quic->local_cnxid_length = lb_ctx->connection_id_length;
                    quic->cnx_id_callback_fn = picoquic_lb_compat_cid_generate;
                    quic->cnx_id_callback_ctx = (void*)lb_ctx;
                }
            }
        }
    }

    return ret;
}

void picoquic_lb_compat_cid_config_free(picoquic_quic_t* quic)
{
    if (quic->cnx_id_callback_fn == picoquic_lb_compat_cid_generate &&
        quic->cnx_id_callback_ctx != NULL) {
        picoquic_load_balancer_config_t* lb_config = (picoquic_load_balancer_config_t*)quic->cnx_id_callback_ctx;
        /* Release the encryption contexts so as to avoid memory leaks */
        /* Free the data */
        free(lb_config);
        /* Reset the Quic context */
        quic->cnx_id_callback_fn = NULL;
        quic->cnx_id_callback_ctx = NULL;
    }
}