/*
* 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.
*/

/*
 * Management of transport parameters for PicoQUIC.
 */

#include "picoquic_internal.h"
#include "picoquic_unified_log.h"
#include "tls_api.h"
#include <string.h>

uint8_t* picoquic_transport_param_varint_encode_old(uint8_t* bytes, const uint8_t* bytes_max, uint64_t n64) 
{
    if (bytes + 2 > bytes_max) {
        bytes = NULL;
    }
    else {
        uint8_t * byte_l;
        size_t l;

        *bytes++ = 0;
        byte_l = bytes;
        *bytes++ = 0;
        l = picoquic_varint_encode(bytes, bytes_max - bytes, n64);
        if (l == 0) {
            bytes = NULL;
        }
        else {
            *byte_l = (uint8_t) l;
            bytes += l;
        }
    }

    return bytes;
}

uint64_t picoquic_transport_param_varint_decode(picoquic_cnx_t * cnx, uint8_t* bytes, uint64_t extension_length, int* ret) 
{
    uint64_t n64 = 0;
    uint64_t l_v = picoquic_varint_decode(bytes, (size_t)extension_length, &n64);

    if (l_v == 0 || l_v != extension_length) {
        *ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
    }

    return n64;
}

uint8_t* picoquic_transport_param_type_varint_encode_old(uint8_t* bytes, const uint8_t* bytes_max, picoquic_tp_enum tp_type, uint64_t n64)
{
    if (bytes != NULL && bytes + 2 <= bytes_max) {
        picoformat_16(bytes, (uint16_t)tp_type);
        bytes = picoquic_transport_param_varint_encode_old(bytes + 2, bytes_max, n64);
    }
    else {
        bytes = NULL;
    }
    return bytes;
}

uint8_t* picoquic_transport_param_varint_encode(uint8_t* bytes, const uint8_t* bytes_max, uint64_t n64)
{
    if (bytes + 1 > bytes_max) {
        bytes = NULL;
    }
    else {
        uint8_t* byte_l = bytes++;
        bytes = picoquic_frames_varint_encode(bytes, bytes_max, n64);
        if (bytes != NULL) {
            *byte_l = (uint8_t)((bytes - byte_l) - 1);
        }
    }

    return bytes;
}

uint8_t* picoquic_transport_param_type_varint_encode(uint8_t* bytes, const uint8_t* bytes_max, picoquic_tp_enum tp_type, uint64_t n64)
{
    if (bytes != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, tp_type)) != NULL) {
        bytes = picoquic_transport_param_varint_encode(bytes, bytes_max, n64);
    }
    return bytes;
}

uint8_t* picoquic_transport_param_type_flag_encode(uint8_t* bytes, const uint8_t* bytes_max, picoquic_tp_enum tp_type)
{
    if (bytes != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, tp_type)) != NULL) {
        bytes = picoquic_frames_varint_encode(bytes, bytes_max, 0);
    }
    return bytes;
}

uint8_t* picoquic_transport_param_cid_encode(uint8_t* bytes, const uint8_t* bytes_max, picoquic_tp_enum tp_type, picoquic_connection_id_t * cid)
{
    if (bytes != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, tp_type)) != NULL) {
        /* frame encoding includes length and the value. */
        bytes = picoquic_frames_cid_encode(bytes, bytes_max, cid);
    }
    return bytes;
}

int picoquic_transport_param_cid_decode(picoquic_cnx_t * cnx, uint8_t* bytes, uint64_t extension_length, picoquic_connection_id_t* cid)
{
    int ret = 0;
    cid->id_len = (uint8_t)picoquic_parse_connection_id(bytes, (uint8_t)extension_length, cid);
    if ((size_t)cid->id_len != extension_length) {
        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
    }

    return ret;
}

uint64_t picoquic_decode_transport_param_stream_id(uint64_t rank, int extension_mode, int stream_type) 
{
    uint64_t stream_id = 0xFFFFFFFFFFFFFFFFull;
    
    if (rank > 0) {
        stream_id = stream_type;
        stream_id += extension_mode^1;

        stream_id += 4 * (rank - 1);
    }

    return stream_id;
}

uint64_t picoquic_prepare_transport_param_stream_id(uint64_t stream_id) 
{
    uint64_t rank = 0;

    if (stream_id != 0xFFFFFFFFFFFFFFFFll) {
        rank = (uint64_t)1 +  (stream_id / 4);
    }

    return rank;
}

uint8_t* picoquic_encode_transport_param_prefered_address_old(uint8_t* bytes, uint8_t* bytes_max,
    picoquic_tp_prefered_address_t* prefered_address)
{
    /* first compute the length */
    uint16_t coded_length = 4u + 2u + 16u + 2u + 1u + prefered_address->connection_id.id_len + 16u;

    if (bytes == NULL || bytes + coded_length > bytes_max) {
        bytes = NULL;
    }
    else {
        picoformat_16(bytes, picoquic_tp_server_preferred_address);
        bytes += 2;
        picoformat_16(bytes, coded_length);
        bytes += 2;
        memcpy(bytes, prefered_address->ipv4Address, 4);
        bytes += 4;
        picoformat_16(bytes, prefered_address->ipv4Port);
        bytes += 2;
        memcpy(bytes, prefered_address->ipv6Address, 16);
        bytes += 16;
        picoformat_16(bytes, prefered_address->ipv4Port);
        bytes += 2;
        *bytes++ = prefered_address->connection_id.id_len;
        bytes += picoquic_format_connection_id(bytes, bytes_max - bytes,
            prefered_address->connection_id);
        memcpy(bytes, prefered_address->statelessResetToken, 16);
        bytes += 16;
    }

    return bytes;
}

uint8_t * picoquic_encode_transport_param_prefered_address(uint8_t * bytes, uint8_t * bytes_max,
    picoquic_tp_prefered_address_t * prefered_address)
{
    /* first compute the length */
    uint64_t coded_length = ((uint64_t)(4 + 2 + 16 + 2 + 1)) + prefered_address->connection_id.id_len + ((uint64_t)16);

    if (bytes != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, picoquic_tp_server_preferred_address)) != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, coded_length)) != NULL){
        if (bytes + coded_length > bytes_max) {
            bytes = NULL;
        }
        else {
            memcpy(bytes, prefered_address->ipv4Address, 4);
            bytes += 4;
            picoformat_16(bytes, prefered_address->ipv4Port);
            bytes += 2;
            memcpy(bytes, prefered_address->ipv6Address, 16);
            bytes += 16;
            picoformat_16(bytes, prefered_address->ipv4Port);
            bytes += 2;
            *bytes++ = prefered_address->connection_id.id_len;
            bytes += picoquic_format_connection_id(bytes, bytes_max - bytes,
                prefered_address->connection_id);
            memcpy(bytes, prefered_address->statelessResetToken, 16);
            bytes += 16;
        }
    }

    return bytes;
}

size_t picoquic_decode_transport_param_prefered_address(uint8_t * bytes, size_t bytes_max,
    picoquic_tp_prefered_address_t * prefered_address)
{
    /* first compute the minimal length */
    size_t byte_index = 0;
    uint8_t cnx_id_length = 0;
    size_t minimal_length = 4u + 2u + 16u + 2u + 1u /* + prefered_address->connection_id.id_len */ + 16u;
    size_t ret = 0;

    if (bytes_max >= minimal_length) {
        memcpy(prefered_address->ipv4Address, bytes + byte_index, 4);
        byte_index += 4;
        prefered_address->ipv4Port = PICOPARSE_16(bytes + byte_index);
        byte_index += 2;
        memcpy(prefered_address->ipv6Address, bytes + byte_index, 16);
        byte_index += 16;
        prefered_address->ipv6Port = PICOPARSE_16(bytes + byte_index);
        byte_index += 2;
        cnx_id_length = bytes[byte_index++];
        if (cnx_id_length > 0 && cnx_id_length <= PICOQUIC_CONNECTION_ID_MAX_SIZE &&
            byte_index + (size_t)cnx_id_length + 16u <= bytes_max &&
            cnx_id_length == picoquic_parse_connection_id(bytes + byte_index, cnx_id_length,
                &prefered_address->connection_id)){
            byte_index += cnx_id_length;
            memcpy(prefered_address->statelessResetToken, bytes + byte_index, 16);
            byte_index += 16;
            ret = byte_index;
            prefered_address->is_defined = 1;
        }
    }

    return ret;
}

int picoquic_prepare_transport_extensions(picoquic_cnx_t* cnx, int extension_mode,
    uint8_t* bytes, size_t bytes_length, size_t* consumed)
{
    int ret = 0;
    uint8_t* bytes_zero = bytes;
    uint8_t* bytes_max = bytes + bytes_length;

    bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_initial_max_stream_data_bidi_local,
        cnx->local_parameters.initial_max_stream_data_bidi_local);

    bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_initial_max_data,
        cnx->local_parameters.initial_max_data);

    if (cnx->local_parameters.initial_max_stream_id_bidir > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_initial_max_streams_bidi,
            picoquic_prepare_transport_param_stream_id(
                cnx->local_parameters.initial_max_stream_id_bidir));
    }

    if (cnx->local_parameters.idle_timeout > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_idle_timeout,
            cnx->local_parameters.idle_timeout);
    }

    bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_max_packet_size,
        cnx->local_parameters.max_packet_size);

    if (cnx->local_parameters.ack_delay_exponent != 3) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_ack_delay_exponent,
            cnx->local_parameters.ack_delay_exponent);
    }

    if (cnx->local_parameters.initial_max_stream_id_unidir > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_initial_max_streams_uni,
            picoquic_prepare_transport_param_stream_id(cnx->local_parameters.initial_max_stream_id_unidir));
    }

    if (cnx->local_parameters.prefered_address.is_defined) {
        bytes = picoquic_encode_transport_param_prefered_address(
            bytes, bytes_max, &cnx->local_parameters.prefered_address);
    }

    if (cnx->local_parameters.migration_disabled != 0 && bytes != NULL) {
        bytes = picoquic_transport_param_type_flag_encode(bytes, bytes_max, picoquic_tp_disable_migration);
    }

    if (cnx->local_parameters.initial_max_stream_data_bidi_remote > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_initial_max_stream_data_bidi_remote,
            cnx->local_parameters.initial_max_stream_data_bidi_remote);
    }

    if (cnx->local_parameters.initial_max_stream_data_uni > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_initial_max_stream_data_uni,
            cnx->local_parameters.initial_max_stream_data_uni);
    }

    if (cnx->local_parameters.active_connection_id_limit > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_active_connection_id_limit,
            cnx->local_parameters.active_connection_id_limit);
    }

    if (cnx->local_parameters.max_ack_delay != PICOQUIC_ACK_DELAY_MAX_DEFAULT) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_max_ack_delay,
            (cnx->local_parameters.max_ack_delay + 999) / 1000); /* Max ACK delay in milliseconds */
    }
    bytes = picoquic_transport_param_cid_encode(bytes, bytes_max, picoquic_tp_handshake_connection_id, &cnx->path[0]->p_local_cnxid->cnx_id);

    if (extension_mode == 1){
        if (cnx->original_cnxid.id_len > 0) {
            bytes = picoquic_transport_param_cid_encode(bytes, bytes_max, picoquic_tp_original_connection_id, &cnx->original_cnxid);
            bytes = picoquic_transport_param_cid_encode(bytes, bytes_max, picoquic_tp_retry_connection_id, &cnx->initial_cnxid);
        }
        else if (cnx->is_hcid_verified) {
            bytes = picoquic_transport_param_cid_encode(bytes, bytes_max, picoquic_tp_original_connection_id, &cnx->initial_cnxid);
        }
    }

    if (extension_mode == 1) {
        if (bytes != NULL &&
            (bytes = picoquic_frames_varint_encode(bytes, bytes_max, picoquic_tp_stateless_reset_token)) != NULL &&
            (bytes = picoquic_frames_varint_encode(bytes, bytes_max, PICOQUIC_RESET_SECRET_SIZE)) != NULL) {
            if (bytes + PICOQUIC_RESET_SECRET_SIZE < bytes_max) {
                (void)picoquic_create_cnxid_reset_secret(cnx->quic, &cnx->path[0]->p_local_cnxid->cnx_id, bytes);
                bytes += PICOQUIC_RESET_SECRET_SIZE;
            }
            else {
                bytes = NULL;
            }
        }
    }

    if (!cnx->client_mode && cnx->local_parameters.max_datagram_frame_size == 0 &&
        cnx->remote_parameters.max_datagram_frame_size > 0) {
        cnx->local_parameters.max_datagram_frame_size = PICOQUIC_MAX_PACKET_SIZE;
    }

    if (cnx->local_parameters.max_datagram_frame_size > 0 && bytes != NULL) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_max_datagram_frame_size,
            cnx->local_parameters.max_datagram_frame_size);
    }

    if (cnx->grease_transport_parameters) {
        /* Do not use a purely random value, so we can repetitive tests */
        int n = 31 * (cnx->initial_cnxid.id[0] + cnx->client_mode) + 27;
        uint64_t v = cnx->initial_cnxid.id[1];
        while (n == picoquic_tp_test_large_chello ||
            n == picoquic_tp_enable_loss_bit_old) {
            n += 31;
        }
        v = (v << 8) + cnx->initial_cnxid.id[2];
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, n, v);
    }

    if (cnx->test_large_chello && bytes != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, picoquic_tp_test_large_chello)) != NULL &&
        (bytes = picoquic_frames_varint_encode(bytes, bytes_max, 1200)) != NULL){
        if (bytes + 1200 > bytes_max) {
            bytes = NULL;
        }
        else {
            memset(bytes, 'Q', 1200);
            bytes += 1200;
        }
    }

    if (cnx->local_parameters.enable_loss_bit > 0 && bytes != NULL) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_enable_loss_bit,
            (cnx->local_parameters.enable_loss_bit > 1) ? 1 : 0);
    }

    if (bytes != NULL && cnx->local_parameters.min_ack_delay > 0) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_min_ack_delay,
            cnx->local_parameters.min_ack_delay);
    }

    if (cnx->local_parameters.enable_time_stamp > 0 && bytes != NULL) {
        bytes = picoquic_transport_param_type_varint_encode(bytes, bytes_max, picoquic_tp_enable_time_stamp,
            cnx->local_parameters.enable_time_stamp);
    }

    if (cnx->local_parameters.do_grease_quic_bit > 0 && bytes != NULL) {
        bytes = picoquic_transport_param_type_flag_encode(bytes, bytes_max, picoquic_tp_grease_quic_bit);
    }

    if (bytes == NULL) {
        *consumed = 0;
        ret = PICOQUIC_ERROR_EXTENSION_BUFFER_TOO_SMALL;
    }
    else {
        *consumed = bytes - bytes_zero;
        picoquic_log_transport_extension(cnx, 1, *consumed, bytes_zero);
    }

    return ret;
}

void picoquic_clear_transport_extensions(picoquic_cnx_t* cnx)
{
    cnx->remote_parameters.initial_max_stream_data_bidi_local = 0;
    picoquic_update_stream_initial_remote(cnx);
    cnx->remote_parameters.initial_max_stream_data_bidi_remote = 0;
    picoquic_update_stream_initial_remote(cnx);
    cnx->remote_parameters.initial_max_stream_data_uni = 0;
    picoquic_update_stream_initial_remote(cnx);
    cnx->remote_parameters.initial_max_data = 0;
    cnx->maxdata_remote = cnx->remote_parameters.initial_max_data;
    cnx->remote_parameters.initial_max_stream_id_bidir = 0;
    cnx->max_stream_id_bidir_remote = 0;
    cnx->remote_parameters.idle_timeout = 0;
    cnx->remote_parameters.max_packet_size = 1500;
    cnx->remote_parameters.ack_delay_exponent = 3;
    cnx->remote_parameters.initial_max_stream_id_unidir = 0;
    cnx->max_stream_id_unidir_remote = 0;
    cnx->remote_parameters.migration_disabled = 0;
    cnx->remote_parameters.max_ack_delay = PICOQUIC_ACK_DELAY_MAX_DEFAULT;
    cnx->remote_parameters.max_datagram_frame_size = 0;
    cnx->remote_parameters.active_connection_id_limit = 0;
    cnx->remote_parameters.enable_loss_bit = 0;
    cnx->remote_parameters.enable_time_stamp = 0;
    cnx->remote_parameters.min_ack_delay = 0;
    cnx->remote_parameters.do_grease_quic_bit = 0;
}

int picoquic_receive_transport_extensions(picoquic_cnx_t* cnx, int extension_mode,
    uint8_t* bytes, size_t bytes_max, size_t* consumed)
{
    int ret = 0;
    size_t byte_index = 0;
    uint64_t present_flag = 0;
    picoquic_connection_id_t original_connection_id = picoquic_null_connection_id;
    picoquic_connection_id_t handshake_connection_id = picoquic_null_connection_id;
    picoquic_connection_id_t retry_connection_id = picoquic_null_connection_id;

    cnx->remote_parameters_received = 1;
    picoquic_clear_transport_extensions(cnx);

    picoquic_log_transport_extension(cnx, 0, bytes_max, bytes);

    /* Set the parameters to default value zero */
    memset(&cnx->remote_parameters, 0, sizeof(picoquic_tp_t));
    /* Except for ack_delay_exponent, whose default is 3 */
    cnx->remote_parameters.ack_delay_exponent = 3;

    while (ret == 0 && byte_index < bytes_max) {
        size_t ll_type = 0;
        size_t ll_length = 0;
        uint64_t extension_type = UINT64_MAX;
        uint64_t extension_length = 0;

        if (byte_index + 2 > bytes_max) {
            ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
        }
        else {
            ll_type = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &extension_type);
            byte_index += ll_type;
            ll_length = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &extension_length);
            byte_index += ll_length;

            if (ll_type == 0 || ll_length == 0 || byte_index + extension_length > bytes_max) {
                ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
            }
            else {
                if (extension_type < 64) {
                    if ((present_flag & (1ull << extension_type)) != 0) {
                        /* Malformed, already present */
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    else {
                        present_flag |= (1ull << extension_type);
                    }
                }

                switch (extension_type) {
                case picoquic_tp_initial_max_stream_data_bidi_local:
                    cnx->remote_parameters.initial_max_stream_data_bidi_local =
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);

                    /* If we sent zero rtt data, the streams were created with the
                     * old value of the remote parameter. We need to update that.
                     */
                    picoquic_update_stream_initial_remote(cnx);
                    break;
                case picoquic_tp_initial_max_stream_data_bidi_remote:
                    cnx->remote_parameters.initial_max_stream_data_bidi_remote =
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    /* If we sent zero rtt data, the streams were created with the
                    * old value of the remote parameter. We need to update that.
                    */
                    picoquic_update_stream_initial_remote(cnx);
                    break;
                case picoquic_tp_initial_max_stream_data_uni:
                    cnx->remote_parameters.initial_max_stream_data_uni =
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    /* If we sent zero rtt data, the streams were created with the
                    * old value of the remote parameter. We need to update that.
                    */
                    picoquic_update_stream_initial_remote(cnx);
                    break;
                case picoquic_tp_initial_max_data:
                    cnx->remote_parameters.initial_max_data =
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    cnx->maxdata_remote = cnx->remote_parameters.initial_max_data;
                    break;
                case picoquic_tp_initial_max_streams_bidi: {
                    uint64_t old_limit = cnx->max_stream_id_bidir_remote;
                    cnx->remote_parameters.initial_max_stream_id_bidir =
                        picoquic_decode_transport_param_stream_id(
                            picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret), extension_mode,
                            PICOQUIC_STREAM_ID_BIDIR);

                    cnx->max_stream_id_bidir_remote =
                        (cnx->remote_parameters.initial_max_stream_id_bidir == 0xFFFFFFFF) ? 0 : cnx->remote_parameters.initial_max_stream_id_bidir;

                    picoquic_add_output_streams(cnx, old_limit, cnx->max_stream_id_bidir_remote, 1);
                    break;
                }
                case picoquic_tp_idle_timeout:
                    cnx->remote_parameters.idle_timeout = (uint32_t)
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    break;

                case picoquic_tp_max_packet_size:
                    cnx->remote_parameters.max_packet_size = (uint32_t)
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    break;
                case picoquic_tp_stateless_reset_token:
                    if (extension_mode != 1) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    else if (extension_length != PICOQUIC_RESET_SECRET_SIZE) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    else {
                        memcpy(cnx->path[0]->reset_secret, bytes + byte_index, PICOQUIC_RESET_SECRET_SIZE);
                    }
                    break;
                case picoquic_tp_ack_delay_exponent:
                    cnx->remote_parameters.ack_delay_exponent = (uint8_t)
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    break;
                case picoquic_tp_initial_max_streams_uni: {
                    uint64_t old_limit = cnx->max_stream_id_unidir_remote;
                    cnx->remote_parameters.initial_max_stream_id_unidir =
                        picoquic_decode_transport_param_stream_id(
                            picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret), extension_mode,
                            PICOQUIC_STREAM_ID_UNIDIR);

                    cnx->max_stream_id_unidir_remote =
                        (cnx->remote_parameters.initial_max_stream_id_unidir == 0xFFFFFFFF) ? 0 : cnx->remote_parameters.initial_max_stream_id_unidir;
                    picoquic_add_output_streams(cnx, old_limit, cnx->max_stream_id_unidir_remote, 0);
                    break;
                }
                case picoquic_tp_server_preferred_address:
                {
                    uint64_t coded_length = picoquic_decode_transport_param_prefered_address(
                        bytes + byte_index, (size_t)extension_length, &cnx->remote_parameters.prefered_address);

                    if (coded_length != extension_length) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    break;
                }
                case picoquic_tp_disable_migration:
                    if (extension_length != 0) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    else {
                        cnx->remote_parameters.migration_disabled = 1;
                    }
                    break;
                case picoquic_tp_max_ack_delay:
                    cnx->remote_parameters.max_ack_delay = (uint32_t)
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret) * 1000;
                    if (cnx->remote_parameters.max_ack_delay > PICOQUIC_MAX_ACK_DELAY_MAX_MS * 1000) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    break;
                case picoquic_tp_original_connection_id:
                    ret = picoquic_transport_param_cid_decode(cnx, bytes + byte_index, extension_length, &original_connection_id);
                    break;
                case picoquic_tp_retry_connection_id:
                    ret = picoquic_transport_param_cid_decode(cnx, bytes + byte_index, extension_length, &retry_connection_id);
                    break;
                case picoquic_tp_handshake_connection_id:
                    ret = picoquic_transport_param_cid_decode(cnx, bytes + byte_index, extension_length, &handshake_connection_id);
                    if (ret == 0) {
                        if (picoquic_compare_connection_id(&cnx->path[0]->remote_cnxid, &handshake_connection_id) != 0) {
                            ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                        }
                        else {
                            cnx->is_hcid_verified = 1;
                        }
                    }
                    break;
                case picoquic_tp_active_connection_id_limit:
                    cnx->remote_parameters.active_connection_id_limit = (uint32_t)
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    /* TODO: may need to check the value, but conditions are unclear */
                    break;
                case picoquic_tp_max_datagram_frame_size:
                    cnx->remote_parameters.max_datagram_frame_size = (uint32_t)
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    break;
                case picoquic_tp_enable_loss_bit_old:
                    /* The old loss bit definition is obsolete */
                    break;
                case picoquic_tp_enable_loss_bit: {
                    uint64_t enabled = picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    if (ret == 0) {
                        if (enabled == 0) {
                            /* Send only variant of loss bit */
                            cnx->remote_parameters.enable_loss_bit = 1;
                        }
                        else if (enabled == 1) {
                            /* Both send and receive are enabled */
                            cnx->remote_parameters.enable_loss_bit = 2;
                        }
                        else {
                            /* Only values 0 and 1 are expected */
                            ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                        }
                    }
                    break;
                }
                case picoquic_tp_min_ack_delay:
                    cnx->remote_parameters.min_ack_delay =
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);
                    /* Values of 0 and values larger that 2^24 are not expected */
                    if (ret == 0 &&
                        (cnx->remote_parameters.min_ack_delay == 0 ||
                            cnx->remote_parameters.min_ack_delay > PICOQUIC_ACK_DELAY_MIN_MAX_VALUE)) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
                    }
                    else {
                        if (cnx->local_parameters.max_ack_delay > 0) {
                            cnx->is_ack_frequency_negotiated = 1;
                        }
                    }
                    break;
                case picoquic_tp_enable_time_stamp: {
                    uint64_t tp_time_stamp =
                        picoquic_transport_param_varint_decode(cnx, bytes + byte_index, extension_length, &ret);

                    if (ret == 0) {
                        if (tp_time_stamp < 1 || tp_time_stamp > 3) {
                            ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                        }
                        else {
                            cnx->remote_parameters.enable_time_stamp = (int)tp_time_stamp;
                        }
                    }
                    break;
                }
                case picoquic_tp_grease_quic_bit:
                    if (extension_length != 0) {
                        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                    }
                    else {
                        cnx->remote_parameters.do_grease_quic_bit = 1;
                    }
                    break;
                default:
                    /* ignore unknown extensions */
                    break;
                }

                if (ret == 0) {
                    byte_index += (size_t)extension_length;
                }
            }
        }
    }

    /* Compute the negotiated version of the time out.
     * The parameter values are expressed in milliseconds,
     * but the connection context variable is in microseconds.
     * If the keep alive interval was set to a too short value,
     * reset it.
     */
    cnx->idle_timeout = cnx->local_parameters.idle_timeout*1000ull;
    if (cnx->local_parameters.idle_timeout == 0 ||
        (cnx->remote_parameters.idle_timeout > 0 && cnx->remote_parameters.idle_timeout < 
            cnx->local_parameters.idle_timeout)) {
        cnx->idle_timeout = cnx->remote_parameters.idle_timeout*1000ull;
    }
    if (cnx->idle_timeout == 0) {
        cnx->idle_timeout = UINT64_MAX;
    }
    else if (cnx->keep_alive_interval != 0 &&
        cnx->keep_alive_interval > cnx->idle_timeout / 2) {
        cnx->keep_alive_interval = cnx->idle_timeout / 2;
    }

    if (ret == 0 && (present_flag & (1ull << picoquic_tp_max_ack_delay)) == 0) {
        cnx->remote_parameters.max_ack_delay = PICOQUIC_ACK_DELAY_MAX_DEFAULT;
    }

    if (ret == 0 && (present_flag & (1ull << picoquic_tp_active_connection_id_limit)) == 0) {
        if (cnx->path[0]->p_local_cnxid->cnx_id.id_len == 0) {
            cnx->remote_parameters.active_connection_id_limit = 0;
        }
        else {
            cnx->remote_parameters.active_connection_id_limit = PICOQUIC_NB_PATH_DEFAULT;
        }
    }

    /* Clients must not include reset token, server address, retry cid or original cid  */

    if (ret == 0 && extension_mode == 0 &&
        ((present_flag & (1ull << picoquic_tp_stateless_reset_token)) != 0 ||
        (present_flag & (1ull << picoquic_tp_server_preferred_address)) != 0 ||
            (present_flag & (1ull << picoquic_tp_original_connection_id)) != 0 ||
            (present_flag & (1ull << picoquic_tp_retry_connection_id)) != 0)) {
        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
    }

    /* In the old versions, there was only one parameter: original CID. In the new versions,
     * there are also retry CID and handshake CID, and the verification logic changed. 
     * If the new extensions are not used and the version is old, we support the
     * old behavior. If the HCID extension is present, we support the new behavior.
     * Most of the verifications happen on the client side, upon receiving server
     * parameters. 
     * TODO: clean up when removing support for version 27.
     */

    if (ret == 0 && picoquic_supported_versions[cnx->version_index].version != PICOQUIC_SEVENTEENTH_INTEROP_VERSION &&
        (present_flag & (1ull << picoquic_tp_handshake_connection_id)) == 0) {
        /* HCID extension becomes mandatory after draft 27 */
        ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
    }

    if (ret == 0 && extension_mode == 1) {
        /* Reeciving server parameters */
        if ((present_flag & (1ull << picoquic_tp_handshake_connection_id)) != 0) {
            /* The HCID extension is present. Verify that the original and retry cnxid are as expected */
            if (cnx->original_cnxid.id_len != 0) {
                /* OCID should be present and match original_cid.
                 * RCID should be present and match initial_cid, since token parsing
                 * verified that initial_cid matches source CID of retry packet. */
                if ((present_flag & (1ull << picoquic_tp_retry_connection_id)) == 0 ||
                    (present_flag & (1ull << picoquic_tp_original_connection_id)) == 0 ||
                    picoquic_compare_connection_id(&cnx->original_cnxid, &original_connection_id) != 0 ||
                    picoquic_compare_connection_id(&cnx->initial_cnxid, &retry_connection_id) != 0) {
                    ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                }
            }
            else {
                /* RCID should not be present, OCID should be present and match initial_cid */
                if ((present_flag & (1ull << picoquic_tp_retry_connection_id)) != 0 ||
                    (present_flag & (1ull << picoquic_tp_original_connection_id)) == 0 ||
                    picoquic_compare_connection_id(&cnx->initial_cnxid, &original_connection_id) != 0) {
                    ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
                }
            }
        }
        else  if (picoquic_supported_versions[cnx->version_index].version == PICOQUIC_SEVENTEENTH_INTEROP_VERSION) {
            /* Old behavior. Original CID only present if retry */
            if (cnx->original_cnxid.id_len != 0 &&
                ((present_flag & (1ull << picoquic_tp_original_connection_id)) == 0 ||
                    picoquic_compare_connection_id(&cnx->original_cnxid, &original_connection_id) != 0)) {
                ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
            }
        }
    }

    /* Loss bit is only enabled if negotiated by both parties */
    cnx->is_loss_bit_enabled_outgoing = (cnx->local_parameters.enable_loss_bit > 1) && (cnx->remote_parameters.enable_loss_bit > 0);
    cnx->is_loss_bit_enabled_incoming = (cnx->local_parameters.enable_loss_bit > 0) && (cnx->remote_parameters.enable_loss_bit > 1);

    /* One way delay and Quic_bit_grease only enabled if asked by client and accepted by server */
    if (cnx->client_mode) {
        cnx->is_time_stamp_enabled = 
            (cnx->local_parameters.enable_time_stamp&1) && (cnx->remote_parameters.enable_time_stamp&2);
        cnx->is_time_stamp_sent =
            (cnx->local_parameters.enable_time_stamp & 2) && (cnx->remote_parameters.enable_time_stamp & 1);
        cnx->do_grease_quic_bit = cnx->local_parameters.do_grease_quic_bit && cnx->remote_parameters.do_grease_quic_bit;
    }
    else
    {
        if (cnx->remote_parameters.enable_time_stamp) {
            int v_local = 0;
            if (cnx->remote_parameters.enable_time_stamp & 1) {
                /* Peer wants TS. Say that we can send. */
                v_local |= 2;
                cnx->is_time_stamp_sent = 1;
            }
            if (cnx->remote_parameters.enable_time_stamp & 2) {
                /* Peer can do TS. Say that we want to receive. */
                v_local |= 1;
                cnx->is_time_stamp_enabled = 1;
            }
            cnx->local_parameters.enable_time_stamp = v_local;
        }
        /* When the one way option is set, the server will grease the quic bit if the client supports that,
         * but will not announce support of the grease quic bit, thus asking the client to not set it */
        cnx->local_parameters.do_grease_quic_bit = cnx->remote_parameters.do_grease_quic_bit && !cnx->quic->one_way_grease_quic_bit;
        cnx->do_grease_quic_bit = cnx->remote_parameters.do_grease_quic_bit;
    }

    /* ACK Frequency is only enabled on server if negotiated by client */
    if (!cnx->client_mode && !cnx->is_ack_frequency_negotiated) {
        cnx->local_parameters.min_ack_delay = 0;
    }

    *consumed = byte_index;

    return ret;
}
