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

/*
* Packet logging.
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <picotls.h>
#include "picoquic_internal.h"
#include "bytestream.h"
#include "tls_api.h"
#include "picoquic_unified_log.h"

void picoquic_log_bytes(FILE* F, uint8_t* bytes, size_t bytes_max)
{
    for (size_t i = 0; i < bytes_max;) {
        fprintf(F, "%04x:  ", (int)i);

        for (int j = 0; j < 16 && i < bytes_max; j++, i++) {
            fprintf(F, "%02x ", bytes[i]);
        }
        fprintf(F, "\n");
    }
}

void picoquic_log_error_packet(FILE* F, uint8_t* bytes, size_t bytes_max, int ret)
{
    fprintf(F, "Packet length %d caused error: %d\n", (int)bytes_max, ret);

    picoquic_log_bytes(F, bytes, bytes_max);

    fprintf(F, "\n");
}

void picoquic_log_time(FILE* F, picoquic_cnx_t* cnx, uint64_t current_time,
    const char* label1, const char* label2)
{
    uint64_t delta_t = (cnx == NULL) ? current_time : current_time - cnx->start_time;
    uint64_t time_sec = delta_t / 1000000;
    uint32_t time_usec = (uint32_t)(delta_t % 1000000);

    fprintf(F, "%s%llu.%06d%s", label1,
        (unsigned long long)time_sec, time_usec, label2);
}

const char * picoquic_log_fin_or_event_name(picoquic_call_back_event_t ev)
{
    char const * text = "unknown";
    switch (ev) {
    case picoquic_callback_stream_data:
        text = "stream data";
        break;
    case picoquic_callback_stream_fin:
        text = "stream fin";
        break;
    case picoquic_callback_stream_reset:
        text = "stream reset";
        break;
    case picoquic_callback_stop_sending:
        text = "stop sending";
        break;
    case picoquic_callback_close:
        text = "connection close";
        break;
    case picoquic_callback_application_close:
        text = "application close";
        break;
    case picoquic_callback_version_negotiation:
        text = "version negotiation";
        break;
    case picoquic_callback_stream_gap:
        text = "stream gap";
        break;
    case picoquic_callback_prepare_to_send:
        text = "ready to send";
        break;
    case picoquic_callback_almost_ready:
        text = "almost ready";
        break;
    case picoquic_callback_ready:
        text = "ready";
        break;
    default:
        break;
    }

    return text;
}

void picoquic_log_prefix_initial_cid64(FILE* F, uint64_t log_cnxid64)
{
    if (log_cnxid64 != 0) {
        fprintf(F, "%016llx: ", (unsigned long long)log_cnxid64);
    }
}

static void picoquic_log_address(FILE* F, const struct sockaddr* addr_peer)
{
    if (addr_peer->sa_family == AF_INET) {
        struct sockaddr_in* s4 = (struct sockaddr_in*)addr_peer;
        uint8_t* addr = (uint8_t*)&s4->sin_addr;

        fprintf(F, "%d.%d.%d.%d:%d",
            addr[0], addr[1], addr[2], addr[3],
            ntohs(s4->sin_port));
    }
    else {
        struct sockaddr_in6* s6 = (struct sockaddr_in6*)addr_peer;
        uint8_t* addr = (uint8_t*)&s6->sin6_addr;

        fprintf(F, "[");
        for (int i = 0; i < 8; i++) {
            if (i != 0) {
                fprintf(F, ":");
            }

            if (addr[2 * i] != 0) {
                fprintf(F, "%x%02x", addr[2 * i], addr[(2 * i) + 1]);
            }
            else {
                fprintf(F, "%x", addr[(2 * i) + 1]);
            }
        }
        fprintf(F, "]:%d", ntohs(s6->sin6_port));
    }
}

void picoquic_log_packet_address(FILE* F, uint64_t log_cnxid64, picoquic_cnx_t* cnx,
    const struct sockaddr* addr_peer, int receiving, size_t length, uint64_t current_time)
{
    uint64_t delta_t = 0;
    uint64_t time_sec = 0;
    uint32_t time_usec = 0;

    picoquic_log_prefix_initial_cid64(F, log_cnxid64);

    fprintf(F, (receiving) ? "Receiving %d bytes from " : "Sending %d bytes to ",
        (int)length);

    picoquic_log_address(F, addr_peer);

    if (cnx != NULL) {
        delta_t = current_time - cnx->start_time;
        time_sec = delta_t / 1000000;
        time_usec = (uint32_t)(delta_t % 1000000);
    }

    fprintf(F, " at T=%llu.%06d (%llx)\n",
        (unsigned long long)time_sec, time_usec,
        (unsigned long long)current_time);
}

char const* picoquic_log_state_name(picoquic_state_enum state)
{
    char const* state_name = "unknown";

    switch (state) {
    case picoquic_state_client_init: 
        state_name = "client_init"; 
        break;
    case picoquic_state_client_init_sent: 
        state_name = "client_init_sent"; 
        break;
    case picoquic_state_client_renegotiate: 
        state_name = "client_renegotiate"; 
        break;
    case picoquic_state_client_retry_received: 
        state_name = "client_retry_received"; 
        break;
    case picoquic_state_client_init_resent: 
        state_name = "client_init_resent"; 
        break;
    case picoquic_state_server_init: 
        state_name = "server_init"; 
        break;
    case picoquic_state_server_handshake:
        state_name = "server_handshake";
        break;
    case picoquic_state_client_handshake_start: 
        state_name = "client_handshake_start"; 
        break;
    case picoquic_state_client_almost_ready: 
        state_name = "client_almost_ready";
        break;
    case picoquic_state_handshake_failure:
        state_name = "handshake_failure";
        break;
    case picoquic_state_handshake_failure_resend:
        state_name = "handshake_failure_resend";
        break;
    case picoquic_state_server_almost_ready:
        state_name = "server_almost_ready";
        break;
    case picoquic_state_server_false_start:
        state_name = "server_false_start";
        break;
    case picoquic_state_client_ready_start:
        state_name = "client_ready_start";
        break;
    case picoquic_state_ready:
        state_name = "ready";
        break;
    case picoquic_state_disconnecting:
        state_name = "disconnecting";
        break;
    case picoquic_state_closing_received:
        state_name = "closing_received";
        break;
    case picoquic_state_closing:
        state_name = "closing"; 
        break;
    case picoquic_state_draining:
        state_name = "draining"; 
        break;
    case picoquic_state_disconnected:
        state_name = "disconnected"; 
        break;
    default:
        break;
    }
    return state_name;
}

char const* picoquic_log_ptype_name(picoquic_packet_type_enum ptype)
{
    char const* ptype_name = "unknown";

    switch (ptype) {
    case picoquic_packet_error:
        ptype_name = "error";
        break;
    case picoquic_packet_version_negotiation:
        ptype_name = "version negotiation";
        break;
    case picoquic_packet_initial:
        ptype_name = "initial";
        break;
    case picoquic_packet_retry:
        ptype_name = "retry";
        break;
    case picoquic_packet_handshake:
        ptype_name = "handshake";
        break;
    case picoquic_packet_0rtt_protected:
        ptype_name = "0rtt protected";
        break;
    case picoquic_packet_1rtt_protected:
        ptype_name = "1rtt protected";
        break;
    default:
        break;
    }

    return ptype_name;
}

char const* picoquic_log_frame_names(uint64_t frame_type)
{
    char const * frame_name = "unknown";
    
    switch ((picoquic_frame_type_enum_t)frame_type) {
    case picoquic_frame_type_padding:
        frame_name = "padding";
        break;
    case picoquic_frame_type_reset_stream:
        frame_name = "reset_stream";
        break;
    case picoquic_frame_type_connection_close:
        frame_name = "connection_close";
        break;
    case picoquic_frame_type_application_close:
        frame_name = "application_close";
        break;
    case picoquic_frame_type_max_data:
        frame_name = "max_data";
        break;
    case picoquic_frame_type_max_stream_data:
        frame_name = "max_stream_data";
        break;
    case picoquic_frame_type_max_streams_bidir:
        frame_name = "max_streams_bidir";
        break;
    case picoquic_frame_type_max_streams_unidir:
        frame_name = "max_streams_unidir";
        break;
    case picoquic_frame_type_ping:
        frame_name = "ping";
        break;
    case picoquic_frame_type_data_blocked:
        frame_name = "data_blocked";
        break;
    case picoquic_frame_type_stream_data_blocked:
        frame_name = "stream_data_blocked";
        break;
    case picoquic_frame_type_streams_blocked_bidir:
        frame_name = "streams_blocked_bidir";
        break;
    case picoquic_frame_type_streams_blocked_unidir:
        frame_name = "streams_blocked_unidir";
        break;
    case picoquic_frame_type_new_connection_id:
        frame_name = "new_connection_id";
        break;
    case picoquic_frame_type_stop_sending:
        frame_name = "stop_sending";
        break;
    case picoquic_frame_type_ack:
        frame_name = "ack";
        break;
    case picoquic_frame_type_path_challenge:
        frame_name = "path_challenge";
        break;
    case picoquic_frame_type_path_response:
        frame_name = "path_response";
        break;
    case picoquic_frame_type_crypto_hs:
        frame_name = "crypto_hs";
        break;
    case picoquic_frame_type_new_token:
        frame_name = "new_token";
        break;
    case picoquic_frame_type_ack_ecn:
        frame_name = "ack_ecn";
        break;
    case picoquic_frame_type_retire_connection_id:
        frame_name = "retire_connection_id";
        break;
    case picoquic_frame_type_handshake_done:
        frame_name = "handshake_done";
        break;
    case picoquic_frame_type_datagram:
    case picoquic_frame_type_datagram_l:
        frame_name = "datagram";
        break;
    case picoquic_frame_type_ack_frequency:
        frame_name = "ack_frequency";
        break;
    case picoquic_frame_type_time_stamp:
        frame_name = "time_stamp";
        break;
    default:
        if (PICOQUIC_IN_RANGE(frame_type, picoquic_frame_type_stream_range_min, picoquic_frame_type_stream_range_max)) {
            frame_name = "stream";
        }
        break;
    }

    return frame_name;
}

char const* picoquic_log_tp_name(uint64_t tp_number)
{
    char const * tp_name = "unknown";

    switch (tp_number) {
    case picoquic_tp_original_connection_id:
        tp_name = "ocid";
        break;
    case picoquic_tp_idle_timeout:
        tp_name = "idle_timeout";
        break;
    case picoquic_tp_stateless_reset_token:
        tp_name = "stateless_reset_token";
        break;
    case picoquic_tp_max_packet_size:
        tp_name = "max_packet_size";
        break;
    case picoquic_tp_initial_max_data:
        tp_name = "initial_max_data";
        break;
    case picoquic_tp_initial_max_stream_data_bidi_local:
        tp_name = "max_stream_data_bidi_local";
        break;
    case picoquic_tp_initial_max_stream_data_bidi_remote:
        tp_name = "max_stream_data_bidi_remote";
        break;
    case picoquic_tp_initial_max_stream_data_uni:
        tp_name = "max_stream_data_uni";
        break;
    case picoquic_tp_initial_max_streams_bidi:
        tp_name = "max_streams_bidi";
        break;
    case picoquic_tp_initial_max_streams_uni:
        tp_name = "max_streams_uni";
        break;
    case picoquic_tp_ack_delay_exponent:
        tp_name = "ack_delay_exponent";
        break;
    case picoquic_tp_max_ack_delay:
        tp_name = "max_ack_delay";
        break;
    case picoquic_tp_disable_migration:
        tp_name = "disable_migration";
        break;
    case picoquic_tp_server_preferred_address:
        tp_name = "server_preferred_address";
        break;
    case picoquic_tp_active_connection_id_limit:
        tp_name = "active_connection_id_limit";
        break;
    case picoquic_tp_retry_connection_id:
        tp_name = "rcid";
        break;
    case picoquic_tp_handshake_connection_id:
        tp_name = "hcid";
        break;
    case picoquic_tp_max_datagram_frame_size:
        tp_name = "max_datagram_frame_size";
        break;
    case picoquic_tp_test_large_chello:
        tp_name = "large_chello";
        break;
    case picoquic_tp_enable_loss_bit_old:
        tp_name = "enable_loss_bit(old)";
        break;
    case picoquic_tp_enable_loss_bit:
        tp_name = "enable_loss_bit";
        break;
    case picoquic_tp_min_ack_delay:
        tp_name = "min_ack_delay";
        break;
    case picoquic_tp_enable_time_stamp:
        tp_name = "enable_time_stamp";
        break;
    case picoquic_tp_grease_quic_bit:
        tp_name = "grease_quic_bit";
        break;
    default:
        break;
    }

    return tp_name;
}

void picoquic_log_connection_id(FILE* F, picoquic_connection_id_t * cid)
{
    fprintf(F, "<");
    for (uint8_t i = 0; i < cid->id_len; i++) {
        fprintf(F, "%02x", cid->id[i]);
    }
    fprintf(F, ">");
}

void picoquic_log_packet_header(FILE* F, uint64_t log_cnxid64, picoquic_packet_header* ph, int receiving)
{
    picoquic_log_prefix_initial_cid64(F, log_cnxid64);

    fprintf(F, "%s packet type: %d (%s), ", (receiving != 0)?"Receiving":"Sending",
        ph->ptype, picoquic_log_ptype_name(ph->ptype));

    fprintf(F, "S%d,", ph->spin);
    fprintf(F, " Q%d,", !ph->quic_bit_is_zero);

    switch (ph->ptype) {
    case picoquic_packet_1rtt_protected:
        /* Short packets. Log dest CID and Seq number. */
        fprintf(F, "\n");
        picoquic_log_prefix_initial_cid64(F, log_cnxid64);
        fprintf(F, "    ");
        picoquic_log_connection_id(F, &ph->dest_cnx_id);
        fprintf(F, ", Seq: %d (%llu), Phi: %d,", ph->pn, (unsigned long long)ph->pn64, ph->key_phase);
        if (ph->has_loss_bits) {
            fprintf(F, " Q(%d), L(%d),", ph->loss_bit_Q, ph->loss_bit_L);
        }
        fprintf(F, "\n");
        break;
    case picoquic_packet_version_negotiation:
        /* V nego. log both CID */
        fprintf(F, "\n");
        picoquic_log_prefix_initial_cid64(F, log_cnxid64);
        fprintf(F, "    ");
        picoquic_log_connection_id(F, &ph->dest_cnx_id);
        fprintf(F, ", ");
        picoquic_log_connection_id(F, &ph->srce_cnx_id);
        fprintf(F, "\n");
        break;
    default:
        /* Long packets. Log Vnum, both CID, Seq num, Payload length */
        fprintf(F, " Version %x,", ph->vn);

        fprintf(F, "\n");
        picoquic_log_prefix_initial_cid64(F, log_cnxid64);
        fprintf(F, "    ");
        picoquic_log_connection_id(F, &ph->dest_cnx_id);
        fprintf(F, ", ");
        picoquic_log_connection_id(F, &ph->srce_cnx_id);
        fprintf(F, ", Seq: %d, pl: %zd\n", ph->pn, ph->pl_val);
        if (ph->ptype == picoquic_packet_initial) {
            picoquic_log_prefix_initial_cid64(F, log_cnxid64);
            fprintf(F, "    Token length: %zd", ph->token_length);
            if (ph->token_length > 0) {
                size_t printed_length = (ph->token_length > 16) ? 16 : ph->token_length;
                fprintf(F, ", Token: ");
                for (size_t i = 0; i < printed_length; i++) {
                    fprintf(F, "%02x", ph->token_bytes[i]);
                }
                if (printed_length < ph->token_length) {
                    fprintf(F, "...");
                }
            }
            fprintf(F, "\n");
        }
        break;
    }
}

void picoquic_log_negotiation_packet(FILE* F, uint64_t log_cnxid64,
    const uint8_t* bytes, size_t length, picoquic_packet_header* ph)
{
    size_t byte_index = ph->offset;
    uint32_t vn = 0;

    picoquic_log_prefix_initial_cid64(F, log_cnxid64);

    fprintf(F, "    versions: ");

    while (byte_index + 4 <= length) {
        vn = PICOPARSE_32(bytes + byte_index);
        byte_index += 4;
        fprintf(F, "%x, ", vn);
    }
    fprintf(F, "\n");
}

void picoquic_log_retry_packet(FILE* F, uint64_t log_cnxid64,
    const uint8_t* bytes, picoquic_packet_header* ph)
{
    size_t byte_index = ph->offset;
    int token_length = 0;
    uint8_t odcil;
    int payload_length = (int)(ph->payload_length);
    int is_err = 0;
    int has_checksum = picoquic_supported_versions[ph->version_index].version_retry_key != NULL;

    if (!has_checksum) {
        odcil = bytes[byte_index];
        byte_index++;
        payload_length--;

        if ((int)odcil > payload_length) {
            picoquic_log_prefix_initial_cid64(F, log_cnxid64);
            fprintf(F, "    packet too short, ODCIL: %d, only %d bytes available.\n",
                odcil, payload_length);
            is_err = 1;
        }
        else {
            /* Dump the old connection ID */
            picoquic_log_prefix_initial_cid64(F, log_cnxid64);
            fprintf(F, "    ODCIL: <");
            for (uint8_t i = 0; i < odcil; i++) {
                fprintf(F, "%02x", bytes[byte_index++]);
            }
            token_length = payload_length - odcil;
            fprintf(F, ">, Token length: %d\n", token_length);
        }
    }
    else {
        int checksum_length = 16;

        if (checksum_length >= payload_length) {
            picoquic_log_prefix_initial_cid64(F, log_cnxid64);
            fprintf(F, "    packet too short, checksum: %d bytes, only %d bytes available.\n",
                checksum_length, payload_length);
            is_err = 1;
        }
        else {
            token_length = payload_length - checksum_length;

            picoquic_log_prefix_initial_cid64(F, log_cnxid64);
            fprintf(F, "    Token length: %d, Checksum length: %d\n",
                token_length, checksum_length);
        }
    }

    /* Print the token if there was no error */
    if (token_length > 0 && !is_err) {
        int printed_length = (token_length > 16) ? 16 : token_length;
        picoquic_log_prefix_initial_cid64(F, log_cnxid64);
        fprintf(F, "    Token: ");
        for (uint8_t i = 0; i < printed_length; i++) {
            fprintf(F, "%02x", bytes[byte_index++]);
        }
        if (printed_length < token_length) {
            fprintf(F, "...");
        }
        fprintf(F, "\n");
    }
}

size_t picoquic_log_stream_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index;
    uint64_t stream_id;
    size_t data_length;
    uint64_t offset;
    int fin;
    int ret = 0;

    int suspended = debug_printf_reset(1);
    ret = picoquic_parse_stream_header(bytes, bytes_max,
        &stream_id, &offset, &data_length, &fin, &byte_index);
    (void)debug_printf_reset(suspended);

    if (ret != 0)
        return bytes_max;

    fprintf(F, "    Stream %" PRIu64 ", offset %" PRIu64 ", length %d, fin = %d", stream_id,
        offset, (int)data_length, fin);

    fprintf(F, ": ");
    for (size_t i = 0; i < 8 && i < data_length; i++) {
        fprintf(F, "%02x", bytes[byte_index + i]);
    }
    fprintf(F, "%s\n", (data_length > 8) ? "..." : "");

    return byte_index + data_length;
}

size_t picoquic_log_ack_frame(FILE* F, uint64_t cnx_id64, const uint8_t* bytes, size_t bytes_max, int is_ecn)
{
    size_t byte_index;
    uint64_t num_block;
    uint64_t largest;
    uint64_t ack_delay;
    uint64_t ecnx3[3];
    int suspended = debug_printf_reset(1);
    int ret;

    ret = picoquic_parse_ack_header(bytes, bytes_max, &num_block,
        &largest, &ack_delay, &byte_index, 0);

    (void)debug_printf_reset(suspended);

    if (ret != 0)
        return bytes_max;

    /* Now that the size is good, print it */
    if (is_ecn) {
        fprintf(F, "    ACK_ECN (nb=%u)", (int)num_block);
    }
    else {
        fprintf(F, "    ACK (nb=%u)", (int)num_block);
    }

    /* decoding the acks */

    for (;;) {
        uint64_t range;
        uint64_t block_to_block;

        if (byte_index >= bytes_max) {
            fprintf(F, "    Malformed ACK RANGE, %d blocks remain.\n", (int)num_block);
            break;
        }

        size_t l_range = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &range);
        if (l_range == 0) {
            byte_index = bytes_max;
            fprintf(F, "    Malformed ACK RANGE, requires %d bytes out of %d", (int)picoquic_varint_skip(bytes),
                (int)(bytes_max - byte_index));
            break;
        }
        else {
            byte_index += l_range;
        }

        range++;

        if (largest + 1 < range) {
            fprintf(F, "\n");
            if (cnx_id64 != 0) {
                fprintf(F, "%" PRIx64 ": ", cnx_id64);
            }
            fprintf(F, "    ack range error: largest=%" PRIu64 ", range=%" PRIu64, largest, range);
            byte_index = bytes_max;
            break;
        }

        if (range <= 1)
            fprintf(F, ", %" PRIu64, largest);
        else
            fprintf(F, ", %" PRIu64 "-%" PRIu64, largest - range + 1, largest);

        if (num_block-- == 0)
            break;

        /* Skip the gap */

        if (byte_index >= bytes_max) {
            fprintf(F, "\n");
            if (cnx_id64 != 0) {
                fprintf(F, "%" PRIx64 ": ", cnx_id64);
            }
            fprintf(F, "    Malformed ACK GAP, %d blocks remain.", (int)num_block);
            byte_index = bytes_max;
            break;
        }
        else {
            size_t l_gap = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &block_to_block);
            if (l_gap == 0) {
                byte_index = bytes_max;
                fprintf(F, "\n");
                if (cnx_id64 != 0) {
                    fprintf(F, "%" PRIx64 ": ", cnx_id64);
                }
                fprintf(F, "    Malformed ACK GAP, requires %d bytes out of %d", (int)picoquic_varint_skip(bytes),
                    (int)(bytes_max - byte_index));
                break;
            }
            else {
                byte_index += l_gap;
                block_to_block += 1;
                block_to_block += range;
            }
        }

        if (largest < block_to_block) {
            fprintf(F, "\n");
            if (cnx_id64 != 0) {
                fprintf(F, "%" PRIx64 ": ", cnx_id64);
            }
            fprintf(F, "    ack gap error: largest=%" PRIu64 ", range=%" PRIu64 ", gap=%" PRIu64,
                largest, range, block_to_block - range);
            byte_index = bytes_max;
            break;
        }

        largest -= block_to_block;
    }

    if (is_ecn) {
        /* Decode the ecn counts */
        for (int ecnx = 0; ecnx < 3; ecnx++) {
            size_t l_ecnx = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &ecnx3[ecnx]);

            if (l_ecnx == 0) {
                fprintf(F, ", incorrect ECN encoding");
                byte_index = bytes_max;
                break;
            }
            else {
                byte_index += l_ecnx;
            }
        }

        fprintf(F, ", ect0=%llu, ect1=%llu, ce=%llu\n",
            (unsigned long long)ecnx3[0], (unsigned long long)ecnx3[1], (unsigned long long)ecnx3[2]);
    }
    else {
        fprintf(F, "\n");
    }

    return byte_index;
}

size_t picoquic_log_reset_stream_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    uint64_t stream_id = 0;
    uint64_t error_code = 0;
    uint64_t offset = 0;

    size_t l1 = 0, l2 = 0, l3 = 0;
    if (bytes_max > 2) {
        l1 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &stream_id);
        byte_index += l1;
        if (l1 > 0) {
            l2 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &error_code);
            byte_index += l2;
        }
        if (l2 > 0) {
            l3 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &offset);
            byte_index += l3;
        }
    }

    if (l1 == 0 || l2 == 0 || l3 == 0) {
        fprintf(F, "    Malformed RESET STREAM, requires %d bytes out of %d\n", (int)(byte_index + ((l1 == 0) ? (picoquic_varint_skip(bytes + 1) + 3) : picoquic_varint_skip(bytes + byte_index))),
            (int)bytes_max);
        byte_index = bytes_max;
    } else {
        fprintf(F, "    RESET STREAM %llu, Error 0x%08x, Offset 0x%llx.\n",
            (unsigned long long)stream_id, (uint32_t)error_code, (unsigned long long)offset);
    }

    return byte_index;
}

size_t picoquic_log_stop_sending_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    size_t l1 = 0;
    size_t l2 = 0;
    uint64_t stream_id;
    uint64_t error_code = 0;

    l1 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &stream_id);
    if (l1 != 0){
        byte_index += l1;
        l2 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &error_code);
        byte_index += l2;
    }

    if (l1 == 0 || l2 == 0 || byte_index > bytes_max) {
        fprintf(F, "    Malformed STOP SENDING, requires more than %d bytes\n", (int)bytes_max);
        return bytes_max;
    }

    fprintf(F, "    STOP SENDING Stream %lld (0x%llx), Error 0x%llx.\n",
        (unsigned long long)stream_id, (unsigned long long)stream_id, (unsigned long long)error_code);

    return byte_index;
}

size_t picoquic_log_generic_close_frame(FILE* F, const uint8_t* bytes, size_t bytes_max, uint8_t ftype, uint64_t cnx_id64)
{
    size_t byte_index = 1;
    uint64_t error_code = 0;
    uint64_t string_length = 0;
    uint64_t offending_frame_type = 0;
    size_t lf = 0;
    size_t l1 = 0;
    size_t l0 = 0;

    if (bytes_max >= 2) {
        l0 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &error_code);
        byte_index += l0;
        if (ftype == picoquic_frame_type_connection_close && l0 != 0) {
            lf = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &offending_frame_type);
            if (lf == 0) {
                byte_index = bytes_max;
            }
            else {
                byte_index += lf;
            }
        }
        if (ftype != picoquic_frame_type_connection_close || lf != 0) {
            l1 = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &string_length);
        }
    }

    if (l1 == 0 || l0 == 0) {
        fprintf(F, "    Malformed %s, requires %d bytes out of %d\n",
            picoquic_log_frame_names(ftype), 
            (int)(byte_index + picoquic_varint_skip(bytes + 3)), (int)bytes_max);
        byte_index = bytes_max;
    }
    else {
        byte_index += l1;

        fprintf(F, "    %s, Error 0x%04x, ", picoquic_log_frame_names(ftype), (uint16_t)error_code);
        if (ftype == picoquic_frame_type_connection_close && 
            offending_frame_type != 0) {
            fprintf(F, "Offending frame 0x%llx, ",
                (unsigned long long)offending_frame_type);
        }
        fprintf(F, "Reason length %llu\n", (unsigned long long)string_length);
        if (byte_index + string_length > bytes_max) {
            fprintf(F, "    Malformed %s, requires %llu bytes out of %llu\n",
                picoquic_log_frame_names(ftype),
                (unsigned long long)(byte_index + string_length), (unsigned long long)bytes_max);
            byte_index = bytes_max;
        }
        else if (string_length > 0) {
            /* Print the UTF8 string */
            char reason_string[49];
            uint64_t printed_length = (string_length > 48) ? 48 : string_length;

            for (uint32_t i = 0; i < printed_length; i++) {
                int c = bytes[byte_index + i];

                if (c < 0x20 || c > 0x7E) {
                    c = '.';
                }
                reason_string[i] = (char) c;
            }
            reason_string[printed_length] = 0;

            if (cnx_id64 != 0) {
                fprintf(F, "%" PRIx64 ": ", cnx_id64);
            }

            fprintf(F, "        Reason: %s", reason_string);
            if (string_length > printed_length) {
                fprintf(F, "...");
            }
            fprintf(F, "\n");

            byte_index += (size_t)string_length;
        }
    }

    return byte_index;
}

size_t picoquic_log_connection_close_frame(FILE* F, const uint8_t* bytes, size_t bytes_max, uint64_t cnx_id64)
{
    return picoquic_log_generic_close_frame(F, bytes, bytes_max, picoquic_frame_type_connection_close, cnx_id64);
}

size_t picoquic_log_application_close_frame(FILE* F, const uint8_t* bytes, size_t bytes_max, uint64_t cnx_id64)
{
    return picoquic_log_generic_close_frame(F, bytes, bytes_max, picoquic_frame_type_application_close, cnx_id64);
}

size_t picoquic_log_max_data_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    uint64_t max_data;

    size_t l1 = picoquic_varint_decode(bytes + 1, bytes_max - 1, &max_data);

    if (1 + l1 > bytes_max) {
        fprintf(F, "    Malformed MAX DATA, requires %d bytes out of %d\n", (int)(1 + l1), (int)bytes_max);
        return bytes_max;
    } else {
        byte_index = 1 + l1;
    }

    fprintf(F, "    MAX DATA: 0x%llx.\n", (unsigned long long)max_data);

    return byte_index;
}

size_t picoquic_log_max_stream_data_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    uint64_t stream_id;
    uint64_t max_data;

    size_t l1 = picoquic_varint_decode(bytes + 1, bytes_max - 1, &stream_id);
    size_t l2 = picoquic_varint_decode(bytes + 1 + l1, bytes_max - 1 - l1, &max_data);

    if (l1 == 0 || l2 == 0) {
        fprintf(F, "    Malformed MAX STREAM DATA, requires %d bytes out of %d\n",
            (int)(1 + l1 + l2), (int)bytes_max);
        return bytes_max;
    } else {
        byte_index = 1 + l1 + l2;
    }

    fprintf(F, "    MAX STREAM DATA, Stream: %" PRIu64 ", max data: 0x%llx.\n",
        stream_id, (unsigned long long)max_data);

    return byte_index;
}

size_t picoquic_log_max_stream_id_frame(FILE* F, const uint8_t* bytes, size_t bytes_max, uint64_t frame_id)
{
    size_t byte_index = 1;
    const size_t min_size = 1 + picoquic_varint_skip(bytes + 1);
    uint64_t rank;

    if (min_size > bytes_max) {
        fprintf(F, "    Malformed %s, requires %d bytes out of %d\n", picoquic_log_frame_names(frame_id),
            (int)min_size, (int)bytes_max);
        return bytes_max;
    }

    /* Now that the size is good, parse and print it */
    byte_index += picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &rank);

    fprintf(F, "    %s: max rank %" PRIu64 ".\n", picoquic_log_frame_names(frame_id), rank);

    return byte_index;
}

size_t picoquic_log_blocked_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    const size_t min_size = 1 + picoquic_varint_skip(bytes + 1);
    uint64_t blocked_offset = 0;

    if (min_size > bytes_max) {
        fprintf(F, "    Malformed BLOCKED, requires %d bytes out of %d\n", (int)min_size, (int)bytes_max);
        return bytes_max;
    }

    /* Now that the size is good, parse and print it */
    byte_index += picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &blocked_offset);

    fprintf(F, "    BLOCKED: offset %" PRIu64 ".\n",
        blocked_offset);

    return byte_index;
}

size_t picoquic_log_stream_blocked_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    const size_t min_size = 1 + picoquic_varint_skip(bytes + 1);
    uint64_t blocked_stream_id;

    if (min_size > bytes_max) {
        fprintf(F, "    Malformed STREAM BLOCKED, requires %d bytes out of %d\n", (int)min_size, (int)bytes_max);
        return bytes_max;
    }

    /* Now that the size is good, parse and print it */
    byte_index += picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &blocked_stream_id);
    byte_index += picoquic_varint_skip(&bytes[byte_index]);

    fprintf(F, "    STREAM BLOCKED: %" PRIu64 ".\n",
        blocked_stream_id);

    return byte_index;
}

size_t picoquic_log_streams_blocked_frame(FILE* F, const uint8_t* bytes, size_t bytes_max, uint64_t frame_id)
{
    size_t byte_index = 1;
    const size_t min_size = 1 + picoquic_varint_skip(bytes + 1);
    uint64_t blocked_stream_rank;

    if (min_size > bytes_max) {
        fprintf(F, "    Malformed %s frame, requires %d bytes out of %d\n", picoquic_log_frame_names(frame_id),
            (int)min_size, (int)bytes_max);
        byte_index =  bytes_max;
    }
    else {
        byte_index += picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &blocked_stream_rank);
        fprintf(F, "    %s: %lld\n", picoquic_log_frame_names(frame_id), (unsigned long long) blocked_stream_rank);
    }

    return byte_index;
}

size_t picoquic_log_new_connection_id_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    size_t min_size = 2u + 16u;
    uint64_t sequence;
    uint64_t retire_before = 0;
    picoquic_connection_id_t new_cnx_id = picoquic_null_connection_id;
    uint8_t l_cid = 0;
    size_t l_seq = 0;
    size_t l_ret = 1;

    l_seq = picoquic_varint_decode(&bytes[byte_index], bytes_max, &sequence);
    min_size += l_seq;
    byte_index += l_seq;
    
    l_ret = picoquic_varint_decode(&bytes[byte_index], bytes_max, &retire_before);
    min_size += l_ret;
    byte_index += l_ret;

    if (byte_index < bytes_max) {
        l_cid = bytes[byte_index++];
    }
    min_size += l_cid;

    if (l_seq == 0 || l_ret == 0 ||  min_size > bytes_max) {
        fprintf(F, "    Malformed NEW CONNECTION ID, requires %d bytes out of %d\n", (int)min_size, (int)bytes_max);
        byte_index = bytes_max;
    }
    else {
        byte_index += picoquic_parse_connection_id(bytes + byte_index, l_cid, &new_cnx_id);
        fprintf(F, "    NEW CONNECTION ID[%d]: 0x", (int)sequence);
        for (int x = 0; x < new_cnx_id.id_len; x++) {
            fprintf(F, "%02x", new_cnx_id.id[x]);
        }
        fprintf(F, ", ");
        for (int x = 0; x < 16; x++) {
            fprintf(F, "%02x", bytes[byte_index++]);
        }
        if (retire_before != 0) {
            fprintf(F, ", retire before: %d", (int)retire_before);
        }
        fprintf(F, "\n");
    }

    return byte_index;
}

size_t picoquic_log_retire_connection_id_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    uint64_t sequence = 0;
    size_t l_seq = 0;


    if (bytes_max > byte_index) {
        l_seq = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &sequence);
        byte_index += l_seq;
    }

    if (l_seq == 0 || byte_index > bytes_max) {
        fprintf(F, "    Malformed RETIRE CONNECTION ID, requires %d bytes out of %d\n", (int)(byte_index + ((l_seq == 0)?1:0)), (int)bytes_max);
        byte_index = bytes_max;
    }
    else {
        fprintf(F, "    RETIRE CONNECTION ID[%d]\n", (int)sequence);
    }

    return byte_index;
}

size_t picoquic_log_new_token_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    size_t min_size = 1;
    size_t l_toklen = 0;
    uint64_t toklen = 0;

    l_toklen = picoquic_varint_decode(&bytes[byte_index], bytes_max, &toklen);

    min_size += l_toklen + (size_t)toklen;

    if (l_toklen == 0 || min_size > bytes_max) {
        fprintf(F, "    Malformed NEW CONNECTION ID, requires %d bytes out of %d\n", (int)min_size, (int)bytes_max);
        return bytes_max;
    } else {
        byte_index += l_toklen;
        fprintf(F, "    NEW TOKEN[%d]: 0x", (int)toklen);
        for (uint64_t x = 0; x < toklen && x < 16u; x++) {
            fprintf(F, "%02x", bytes[byte_index + x]);
        }
        byte_index += (size_t)toklen;

        if (toklen > 16) {
            fprintf(F, "...");
        }
        fprintf(F, "\n");
    }

    return byte_index;
}

size_t picoquic_log_path_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    size_t byte_index = 1;
    size_t challenge_length = 8;

    if (byte_index + challenge_length > bytes_max) {
        fprintf(F, "    Malformed %s frame, %d bytes needed, %d available\n",
            picoquic_log_frame_names(bytes[0]),
            (int)(challenge_length + 1), (int)bytes_max);
        byte_index = bytes_max;
    } else {
        fprintf(F, "    %s: ", picoquic_log_frame_names(bytes[0]));

        for (size_t i = 0; i < challenge_length; i++) {
            fprintf(F, "%02x", bytes[byte_index + i]);
        }

        fprintf(F, "\n");

        byte_index += challenge_length;
    }

    return byte_index;
}

size_t picoquic_log_crypto_hs_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    uint64_t offset=0;
    uint64_t data_length = 0;
    size_t byte_index = 1;
    size_t l_off = 0;
    size_t l_len = 0;

    if (bytes_max > byte_index) {
        l_off = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &offset);
        byte_index += l_off;
    }

    if (bytes_max > byte_index) {
        l_len = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &data_length);
        byte_index += l_len;
    }

    if (l_off == 0 || l_len == 0 || byte_index + data_length > bytes_max) {
        fprintf(F, "    Malformed Crypto HS frame.\n");
        byte_index = bytes_max;
    } else {
        fprintf(F, "    Crypto HS frame, offset %" PRIu64 ", length %d", offset, (int)data_length);

        fprintf(F, ": ");
        for (size_t i = 0; i < 8 && i < data_length; i++) {
            fprintf(F, "%02x", bytes[byte_index + i]);
        }
        fprintf(F, "%s\n", (data_length > 8) ? "..." : "");

        byte_index += (size_t)data_length;
    }

    return byte_index;
}

size_t picoquic_log_datagram_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    uint8_t frame_id = bytes[0];
    unsigned int has_length = frame_id & 1;
    size_t l_l = 0;
    uint64_t length = 0;
    size_t byte_index = 1;

    if (has_length) {
        if (bytes_max > byte_index) {
            l_l = picoquic_varint_decode(bytes + byte_index, bytes_max - byte_index, &length);
            byte_index += l_l;
        }
    }
    else {
        length = bytes_max - byte_index;
    }

    if ((has_length && l_l == 0) || byte_index + length > bytes_max) {
        /* log format error */
        fprintf(F, "    Malformed Datagram frame: ");
        for (size_t i = 0; i < bytes_max && i < 8; i++) {
            fprintf(F, "%02x", bytes[i]);
        }
        if (bytes_max > 8) {
            fprintf(F, "...");
        }
        fprintf(F, "\n");

        byte_index = bytes_max;
    }
    else {
        fprintf(F, "    Datagram frame");
        fprintf(F, ", length: %d: ", (int)length);
        for (size_t i = 0; i < 8 && i < length; i++) {
            fprintf(F, "%02x", bytes[byte_index + i]);
        }
        fprintf(F, "%s\n", (length > 8) ? "..." : "");

        byte_index += (size_t)length;
    }

    return byte_index;
}

size_t picoquic_log_ack_frequency_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    uint64_t sequence = 0;
    uint64_t packets = 0;
    uint64_t microsecs = 0;
    const uint8_t* bytes_end = bytes + bytes_max;
    const uint8_t* bytes0 = bytes;
    size_t byte_index = 0;


    if ((bytes = picoquic_frames_varint_skip(bytes, bytes_end)) == NULL ||
        (bytes = picoquic_parse_ack_frequency_frame(bytes, bytes_end, &sequence, &packets, &microsecs)) == NULL) {
        fprintf(F, "    Malformed ACK Frequency frame: ");
        /* log format error */
        for (size_t i = 0; i < bytes_max && i < 8; i++) {
            fprintf(F, "%02x", bytes0[i]);
        }
        if (bytes_max > 8) {
            fprintf(F, "...");
        }
        fprintf(F, "\n");
        byte_index = bytes_max;
    }
    else {
        fprintf(F, "    ACK Frequency: S=%" PRIu64 ", P=%" PRIu64 ", uS=%" PRIu64 "\n", 
            sequence, packets, microsecs);
        byte_index = bytes - bytes0;
    }

    return byte_index;
}

size_t picoquic_log_time_stamp_frame(FILE* F, const uint8_t* bytes, size_t bytes_max)
{
    uint64_t time_stamp = 0;
    const uint8_t* bytes_end = bytes + bytes_max;
    const uint8_t* bytes0 = bytes;
    size_t byte_index = 0;


    if ((bytes = picoquic_frames_varint_skip(bytes, bytes_end)) == NULL ||
        (bytes = picoquic_frames_varint_decode(bytes, bytes_end, &time_stamp)) == NULL) {
        fprintf(F, "    Malformed Time Stamp frame: ");
        /* log format error */
        for (size_t i = 0; i < bytes_max && i < 8; i++) {
            fprintf(F, "%02x", bytes0[i]);
        }
        if (bytes_max > 8) {
            fprintf(F, "...");
        }
        fprintf(F, "\n");
        byte_index = bytes_max;
    }
    else {
        fprintf(F, "    Time Stamp: %" PRIu64 "\n",
            time_stamp);
        byte_index = bytes - bytes0;
    }

    return byte_index;
}

void picoquic_log_frames(FILE* F, uint64_t cnx_id64, const uint8_t* bytes, size_t length)
{
    size_t byte_index = 0;

    while (byte_index < length) {
        uint64_t frame_id = 0;
        size_t frame_id_ll = picoquic_varint_decode(bytes + byte_index, length - byte_index, &frame_id);

        picoquic_log_prefix_initial_cid64(F, cnx_id64);

        if (frame_id_ll == 0 || (frame_id < 64 && frame_id_ll != 1)) {
            size_t id_length = length - byte_index;
            char const* id_more = "";
            if (id_length > 8) {
                    id_length = 8;
                    id_more = "...";
            }
            fprintf(F, "    Incorrect frame id: ");
            for (size_t x = 0; x < id_length; x++) {
                fprintf(F, "%02x", bytes[byte_index++]);
            }
            fprintf(F, "%s\n", id_more);
            byte_index = length;
            continue;
        }

        if (PICOQUIC_IN_RANGE(frame_id, picoquic_frame_type_stream_range_min, picoquic_frame_type_stream_range_max)) {
            byte_index += picoquic_log_stream_frame(F, bytes + byte_index, length - byte_index);
            continue;
        }

        switch (frame_id) {
        case picoquic_frame_type_ack:
            byte_index += picoquic_log_ack_frame(F, cnx_id64, bytes + byte_index, length - byte_index, 0);
            break;
        case picoquic_frame_type_ack_ecn:
            byte_index += picoquic_log_ack_frame(F, cnx_id64, bytes + byte_index, length - byte_index, 1);
            break;
        case picoquic_frame_type_retire_connection_id:
            byte_index += picoquic_log_retire_connection_id_frame(F, bytes + byte_index, length - byte_index);
            break;
        case picoquic_frame_type_padding:
        case picoquic_frame_type_ping: {
            int nb = 0;

            while (byte_index < length && bytes[byte_index] == frame_id) {
                byte_index++;
                nb++;
            }

            fprintf(F, "    %s, %d bytes\n", picoquic_log_frame_names(frame_id), nb);
            break;
        }
        case picoquic_frame_type_reset_stream: /* RST_STREAM */
            byte_index += picoquic_log_reset_stream_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_connection_close: /* CONNECTION_CLOSE */
            byte_index += picoquic_log_connection_close_frame(F, bytes + byte_index,
                length - byte_index, cnx_id64);
            break;
        case picoquic_frame_type_application_close:
            byte_index += picoquic_log_application_close_frame(F, bytes + byte_index,
                length - byte_index, cnx_id64);
            break;
        case picoquic_frame_type_max_data: /* MAX_DATA */
            byte_index += picoquic_log_max_data_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_max_stream_data: /* MAX_STREAM_DATA */
            byte_index += picoquic_log_max_stream_data_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_max_streams_bidir: /* MAX_STREAM_ID */
        case picoquic_frame_type_max_streams_unidir: /* MAX_STREAM_ID */
            byte_index += picoquic_log_max_stream_id_frame(F, bytes + byte_index,
                length - byte_index, frame_id);
            break;
        case picoquic_frame_type_data_blocked: /* BLOCKED */
            /* No payload */
            byte_index += picoquic_log_blocked_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_stream_data_blocked: /* STREAM_BLOCKED */
            byte_index += picoquic_log_stream_blocked_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_streams_blocked_bidir: /* STREAM_ID_NEEDED */
        case picoquic_frame_type_streams_blocked_unidir: /* STREAM_ID_NEEDED */
            byte_index += picoquic_log_streams_blocked_frame(F, bytes + byte_index,
                length - byte_index, frame_id);
            break;
        case picoquic_frame_type_new_connection_id: /* NEW_CONNECTION_ID */
            byte_index += picoquic_log_new_connection_id_frame(F, bytes + byte_index, length - byte_index);
            break;
        case picoquic_frame_type_stop_sending: /* STOP_SENDING */
            byte_index += picoquic_log_stop_sending_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_path_challenge:
            byte_index += picoquic_log_path_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_path_response:
            byte_index += picoquic_log_path_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_crypto_hs:
            byte_index += picoquic_log_crypto_hs_frame(F, bytes + byte_index,
                length - byte_index);
            break;
        case picoquic_frame_type_new_token:
            byte_index += picoquic_log_new_token_frame(F, bytes + byte_index, length - byte_index);
            break;
        case picoquic_frame_type_handshake_done: 
            fprintf(F, "    %s\n", picoquic_log_frame_names(frame_id));
            byte_index++;
            break;
        case picoquic_frame_type_datagram:
        case picoquic_frame_type_datagram_l:
            byte_index += picoquic_log_datagram_frame(F, bytes + byte_index, length - byte_index);
            break;
        case picoquic_frame_type_ack_frequency:
            byte_index += picoquic_log_ack_frequency_frame(F, bytes + byte_index, length - byte_index);
            break;
        case picoquic_frame_type_time_stamp:
            byte_index += picoquic_log_time_stamp_frame(F, bytes + byte_index, length - byte_index);
            break;

        default: {
            /* Not implemented yet! */
            fprintf(F, "    Unknown frame, type: %" PRIu64 " (0x", frame_id);
            for (size_t i = 0; i < 8 && byte_index + i < length; i++) {
                fprintf(F, "%02x", bytes[byte_index + i]);
            }
            if (byte_index + 8 < length) {
                fprintf(F, "... + %zu bytes)\n", length - byte_index - 8);
            }
            else {
                fprintf(F, ")");
            }
            byte_index = length;
            break;
        }
        }
    }
}

void picoquic_log_decrypted_segment(void* F_log, int log_cnxid, picoquic_cnx_t* cnx,
    int receiving, picoquic_packet_header * ph, const uint8_t* bytes, size_t length, int ret)
{
    uint64_t log_cnxid64 = 0;
    FILE * F = (FILE *)F_log;

    if (F == NULL) {
        return;
    }

    if (log_cnxid != 0) {
        if (cnx == NULL) {
            ph->pn64 = ph->pn;
            if (ret == 0) {
                if (ph->ptype == picoquic_packet_version_negotiation) {
                    log_cnxid64 = picoquic_val64_connection_id(ph->srce_cnx_id);
                }
                else {
                    log_cnxid64 = picoquic_val64_connection_id(ph->dest_cnx_id);
                }
            }
        }
        else {
            log_cnxid64 = picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx));
        }
    }
    /* Header */
    picoquic_log_packet_header(F, log_cnxid64, ph, receiving);

    if (ret != 0) {
        /* packet does parse or decrypt */
        picoquic_log_prefix_initial_cid64(F, log_cnxid64);

        if (ret == PICOQUIC_ERROR_STATELESS_RESET) {
            fprintf(F, "   Stateless reset.\n");
        }
        else {
            fprintf(F, "   Header or encryption error: %x.\n", ret);
        }
    }
    else if (ph->ptype == picoquic_packet_version_negotiation) {
        /* log version negotiation */
        picoquic_log_negotiation_packet(F, log_cnxid64, bytes, length, ph);
    }
    else if (ph->ptype == picoquic_packet_retry) {
        /* log version negotiation */
        picoquic_log_retry_packet(F, log_cnxid64, bytes, ph);
    }
    else if (ph->ptype != picoquic_packet_error) {
        /* log frames inside packet */
        picoquic_log_prefix_initial_cid64(F, log_cnxid64);
        fprintf(F, "    %s %d bytes\n", (receiving)?"Decrypted": "Prepared",
            (int)ph->payload_length);
        picoquic_log_frames(F, log_cnxid64, bytes + ph->offset, ph->payload_length);
    }
    fprintf(F, "\n");
}

void picoquic_log_outgoing_segment(void* F_log, int log_cnxid, picoquic_cnx_t* cnx,
    uint8_t * bytes,
    uint64_t sequence_number,
    size_t length,
    uint8_t* send_buffer, size_t send_length, size_t pn_length)
{
    picoquic_cnx_t* pcnx = cnx;
    picoquic_packet_header ph;
    struct sockaddr_in default_addr;
    size_t checksum_length = 16;
    int ret;

    if (F_log == NULL) {
        return;
    }
    memset(&default_addr, 0, sizeof(struct sockaddr_in));
    default_addr.sin_family = AF_INET;

    ret = picoquic_parse_packet_header((cnx == NULL) ? NULL : cnx->quic, send_buffer, send_length,
        ((cnx == NULL || cnx->path[0] == NULL) ? (struct sockaddr *)&default_addr :
        (struct sockaddr *)&cnx->path[0]->local_addr), &ph, &pcnx, 0);

    ph.pn64 = sequence_number;
    ph.pn = (uint32_t)ph.pn64;
    if (ph.ptype != picoquic_packet_retry) {
        if (cnx != NULL) {
            picoquic_epoch_enum epoch = (ph.ptype == picoquic_packet_1rtt_protected) ? picoquic_epoch_1rtt :
                ((ph.ptype == picoquic_packet_0rtt_protected) ? picoquic_epoch_0rtt :
                ((ph.ptype == picoquic_packet_handshake) ? picoquic_epoch_handshake : picoquic_epoch_initial));
            if (cnx->crypto_context[epoch].aead_encrypt != NULL) {
                checksum_length = picoquic_get_checksum_length(cnx, epoch);
            }
        }

        if (ph.pn_offset != 0) {
            ph.offset = ph.pn_offset + pn_length;
            ph.payload_length -= pn_length;
        }
    }
    if (ph.ptype != picoquic_packet_version_negotiation) {
        if (ph.payload_length > checksum_length) {
            ph.payload_length -= (uint16_t)checksum_length;
        }
        else {
            ph.payload_length = 0;
        }
    }
    /* log the segment. */
    picoquic_log_decrypted_segment(F_log, log_cnxid, cnx, 0,
        &ph, bytes, length, ret);
}

void picoquic_log_processing(FILE* F, picoquic_cnx_t* cnx, size_t length, int ret)
{
    fprintf(F, "Processed %d bytes, state = %d (%s), return %d\n\n",
        (int)length, cnx->cnx_state,
        picoquic_log_state_name(cnx->cnx_state),
        ret);
}

void picoquic_log_transport_extension_content(FILE* F, int log_cnxid, uint64_t cnx_id_64,
    uint8_t* bytes, size_t bytes_max)
{
    int ret = 0;
    size_t byte_index = 0;

    if (bytes_max < 256)
    {
        if (ret == 0)
        {
            size_t extensions_size = bytes_max;
            size_t extensions_end;

            extensions_end = byte_index + extensions_size;

            if (log_cnxid != 0) {
                picoquic_log_prefix_initial_cid64(F, cnx_id_64);
            }
            fprintf(F, "    Extension list (%d bytes):\n",
                (uint32_t)extensions_size);
            while (ret == 0 && byte_index < extensions_end) {
                uint64_t extension_type = 0;
                uint64_t extension_length = 0;
                size_t ll_type = 0;
                size_t ll_length = 0;

                ll_type = picoquic_varint_decode(bytes + byte_index, extensions_end - byte_index, &extension_type);
                byte_index += ll_type;
                ll_length = picoquic_varint_decode(bytes + byte_index, extensions_end - byte_index, &extension_length);
                byte_index += ll_length;

                if (ll_type == 0 || ll_length == 0 || byte_index + extension_length > extensions_end) {
                    if (log_cnxid != 0) {
                        picoquic_log_prefix_initial_cid64(F, cnx_id_64);
                    }
                    fprintf(F, "        Malformed extension -- only %d bytes avaliable for type and length.\n",
                        (int)(extensions_end - byte_index));
                    ret = -1;
                }
                else {
                    if (log_cnxid != 0) {
                        picoquic_log_prefix_initial_cid64(F, cnx_id_64);
                    }
                    fprintf(F, "        Extension type: %d (%s), length %d%s",
                        (int)extension_type, picoquic_log_tp_name(extension_type), (int)extension_length,
                        (extension_length == 0) ? "" : ", ");

                    if (byte_index + extension_length > extensions_end) {
                        if (log_cnxid != 0) {
                            picoquic_log_prefix_initial_cid64(F, cnx_id_64);
                        }
                        fprintf(F, "Malformed extension, only %d bytes available.\n", (int)(extensions_end - byte_index));
                        ret = -1;
                    }
                    else {
                        for (uint16_t i = 0; i < extension_length; i++) {
                            fprintf(F, "%02x", bytes[byte_index++]);
                        }
                        fprintf(F, "\n");
                    }
                }
            }
        }

        if (ret == 0 && byte_index < bytes_max) {
            if (log_cnxid != 0) {
                picoquic_log_prefix_initial_cid64(F, cnx_id_64);
            }
            fprintf(F, "    Remaining bytes (%d)\n", (uint32_t)(bytes_max - byte_index));
        }
    }
    else {
        if (log_cnxid != 0) {
            picoquic_log_prefix_initial_cid64(F, cnx_id_64);
        }
        fprintf(F, "Received transport parameter TLS extension (%d bytes):\n", (uint32_t)bytes_max);
        if (log_cnxid != 0) {
            picoquic_log_prefix_initial_cid64(F, cnx_id_64);
        }
        fprintf(F, "    First bytes (%d):\n", (uint32_t)(bytes_max - byte_index));
    }

    if (ret == 0)
    {
        while (byte_index < bytes_max && byte_index < 128) {
            if (log_cnxid != 0) {
                fprintf(F, "%" PRIx64 ": ", cnx_id_64);
            }
            fprintf(F, "        ");
            for (int i = 0; i < 32 && byte_index < bytes_max && byte_index < 128; i++) {
                fprintf(F, "%02x", bytes[byte_index++]);
            }
            fprintf(F, "\n");
        }
    }
}

void picoquic_textlog_transport_extension(FILE* F, picoquic_cnx_t* cnx, int received, int log_cnxid, uint8_t* bytes, size_t bytes_max)
{
    uint64_t cnx_id_64 = (log_cnxid) ? picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)) : 0;

    picoquic_log_prefix_initial_cid64(F, cnx_id_64);
    fprintf(F, "%s transport parameter TLS extension (%d bytes):\n", (received) ? "Received" : "Sending", (uint32_t)bytes_max);
    picoquic_log_transport_extension_content(F, log_cnxid, cnx_id_64, bytes, bytes_max);
}

void picoquic_log_transport_ids(FILE* F, picoquic_cnx_t* cnx, int log_cnxid)
{
    char const* sni = picoquic_tls_get_sni(cnx);
    char const* alpn = picoquic_tls_get_negotiated_alpn(cnx);
    uint64_t cnx_id64 = (log_cnxid) ? picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)) : 0;

    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    if (sni == NULL) {
        fprintf(F, "SNI not received.\n");
    } else {
        fprintf(F, "Received SNI: %s\n", sni);
    }

    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    if (alpn == NULL) {
        fprintf(F, "ALPN not received.\n");
    } else {
        fprintf(F, "Received ALPN: %s\n", alpn);
    }
}

void picoquic_textlog_negotiated_alpn(FILE* F, picoquic_cnx_t* cnx, int received, int log_cnxid, const ptls_iovec_t* list, size_t count)
{
    uint64_t cnx_id_64 = (log_cnxid) ? picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)) : 0;

    picoquic_log_prefix_initial_cid64(F, cnx_id_64);

    fprintf(F, "%s ALPN list (%d): ", (received) ? "Received" : "Sending", (uint32_t)count);

    for (size_t i = 0; i < count; i++) {
        char alpn_target[64];

        if (list[i].len < 64) {
            memcpy(alpn_target, list[i].base, list[i].len);
            alpn_target[list[i].len] = 0;
        }
        else {
            memcpy(alpn_target, list[i].base, 60);
            alpn_target[60] = '.';
            alpn_target[61] = '.';
            alpn_target[62] = '.';
            alpn_target[63] = 0;
        }

        fprintf(F, "%s%s", (i == 0) ? "" : ", ", alpn_target);
    }

    fprintf(F, "\n");
}

void picoquic_log_congestion_state(FILE* F, picoquic_cnx_t* cnx, uint64_t current_time)
{
    picoquic_path_t * path_x = cnx->path[0];

    fprintf(F, "%" PRIx64 ": ", picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)));
    picoquic_log_time(F, cnx, current_time, "T= ", ", ");
    fprintf(F, "cwin: %d,", (int)path_x->cwin);
    fprintf(F, "flight: %d,", (int)path_x->bytes_in_transit);
    fprintf(F, "nb_ret: %d,", (int)cnx->nb_retransmission_total);
    fprintf(F, "rtt_min: %d,", (int)path_x->rtt_min);
    fprintf(F, "rtt: %d,", (int)path_x->smoothed_rtt);
    fprintf(F, "rtt_var: %d,", (int)path_x->rtt_variant);
    fprintf(F, "max_ack_delay: %d,", (int)path_x->max_ack_delay);
    fprintf(F, "state: %d\n", (int)cnx->cnx_state);
}

/*
    From TLS 1.3 spec:
   struct {
       uint32 ticket_lifetime;
       uint32 ticket_age_add;
       opaque ticket_nonce<0..255>;
       opaque ticket<1..2^16-1>;
       Extension extensions<0..2^16-2>;
   } NewSessionTicket;

   struct {
       ExtensionType extension_type;
       opaque extension_data<0..2^16-1>;
   } Extension;
*/
static void picoquic_textlog_tls_ticket(FILE* F, picoquic_connection_id_t cnx_id,
    uint8_t* ticket, uint16_t ticket_length)
{
    uint64_t cnx_id64 = picoquic_val64_connection_id(cnx_id);
    uint32_t lifetime = 0;
    uint32_t age_add = 0;
    uint8_t nonce_length = 0;
    uint16_t ticket_val_length = 0;
    uint16_t extension_length = 0;
    uint8_t* extension_ptr = NULL;
    uint16_t byte_index = 0;
    uint16_t min_length = 4 + 4 + 1 + 2 + 2;
    int ret = 0;

    if (ticket_length < min_length) {
        ret = -1;
    } else {
        lifetime = PICOPARSE_32(ticket);
        byte_index += 4;
        age_add = PICOPARSE_32(ticket + byte_index);
        byte_index += 4;
        nonce_length = ticket[byte_index++];
        min_length += nonce_length;
        if (ticket_length < min_length) {
            ret = -1;
        } else {
            byte_index += nonce_length;

            ticket_val_length = PICOPARSE_16(ticket + byte_index);
            byte_index += 2;
            min_length += ticket_val_length;
            if (ticket_length < min_length) {
                ret = -1;
            } else {
                byte_index += ticket_val_length;

                extension_length = PICOPARSE_16(ticket + byte_index);
                byte_index += 2;
                if (extension_length > ticket_length - min_length){
                    ret = -2;
                } else {
                    extension_ptr = &ticket[byte_index];
                    min_length += extension_length;
                }
            }
        }
    }

    if (ret == -1) {
        picoquic_log_prefix_initial_cid64(F, cnx_id64);
        fprintf(F, "Malformed ticket, length = %d, at least %d required.\n", ticket_length, min_length);
    }
    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    fprintf(F, "lifetime = %d, age_add = %x, %d nonce, %d ticket, %d extensions.\n",
        lifetime, age_add, nonce_length, ticket_val_length, extension_length);

    if (extension_ptr != NULL) {
        uint16_t x_index = 0;

        picoquic_log_prefix_initial_cid64(F, cnx_id64);
        fprintf(F, "ticket extensions: ");

        while (x_index + 4 < extension_length) {
            uint16_t x_type = PICOPARSE_16(extension_ptr + x_index);
            uint16_t x_len = PICOPARSE_16(extension_ptr + x_index + 2);
            x_index += 4 + x_len;

            if (x_type == 42 && x_len == 4) {
                uint32_t ed_len = PICOPARSE_32(extension_ptr + x_index - 4);
                fprintf(F, "%d(ED: %x),", x_type, ed_len);
            } else {
                fprintf(F, "%d (%d bytes),", x_type, x_len);
            }

            if (x_index > extension_length) {
                fprintf(F, "\n");
                picoquic_log_prefix_initial_cid64(F, cnx_id64);
                fprintf(F, "malformed extensions, require %d bytes, not just %d", x_index, extension_length);
            }
        }

        fprintf(F, "\n");

        if (x_index < extension_length) {
            picoquic_log_prefix_initial_cid64(F, cnx_id64);
            fprintf(F, "%d extra bytes at the end of the extensions\n", extension_length - x_index);
        }
    }

    if (ret == -2) {
        picoquic_log_prefix_initial_cid64(F, cnx_id64);
        fprintf(F, "Malformed TLS ticket, %d extra bytes.\n", ticket_length - min_length);
    }
}

/*

From Picotls code:
uint64_t time;
uint16_t cipher_suite;
24 bit int = length of ticket;
<TLS ticket>
16 bit length
<resumption secret>

 */

void picoquic_log_picotls_ticket(FILE* F, picoquic_connection_id_t cnx_id,
    uint8_t* ticket, uint16_t ticket_length)
{
    uint64_t cnx_id64 = picoquic_val64_connection_id(cnx_id);
    uint64_t ticket_time = 0;
    uint16_t kx_id = 0;
    uint16_t suite_id = 0;
    uint32_t tls_ticket_length = 0;
    uint8_t* tls_ticket_ptr = NULL;
    uint16_t secret_length = 0;
    /* uint8_t* secret_ptr = NULL; */
    uint16_t byte_index = 0;
    uint32_t min_length = 8 + 2 + 3 + 2;
    int ret = 0;

    if (ticket_length < min_length) {
        ret = -1;
    } else {
        ticket_time = PICOPARSE_64(ticket);
        byte_index += 8;
        kx_id = PICOPARSE_16(ticket + byte_index);
        byte_index += 2;
        suite_id = PICOPARSE_16(ticket + byte_index);
        byte_index += 2;
        tls_ticket_length = PICOPARSE_24(ticket + byte_index);
        byte_index += 3;
        min_length += tls_ticket_length;
        if (ticket_length < min_length) {
            ret = -1;
        } else {
            tls_ticket_ptr = &ticket[byte_index];
            byte_index += (uint16_t) tls_ticket_length;

            secret_length = PICOPARSE_16(ticket + byte_index);
            min_length += secret_length + 2;
            if (ticket_length < min_length) {
                ret = -1;
            } else {
                /* secret_ptr = &ticket[byte_index]; */
                if (ticket_length > min_length) {
                    ret = -2;
                }
            }
        }
    }

    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    fprintf(F, "ticket time = %llu, kx = %x, suite = %x, %d ticket, %d secret.\n",
        (unsigned long long)ticket_time,
        kx_id, suite_id, tls_ticket_length, secret_length);

    if (ret == -1) {
        picoquic_log_prefix_initial_cid64(F, cnx_id64);
        fprintf(F, "Malformed PTLS ticket, length = %d, at least %d required.\n", 
            ticket_length, min_length);
    } else {
        if (tls_ticket_length > 0 && tls_ticket_ptr != NULL) {
            picoquic_textlog_tls_ticket(F, cnx_id, tls_ticket_ptr, (uint16_t) tls_ticket_length);
        }
    }

    if (ret == -2) {
        picoquic_log_prefix_initial_cid64(F, cnx_id64);
        fprintf(F, "Malformed PTLS ticket, %d extra bytes.\n", ticket_length - min_length);
    }
}

void picoquic_log_retry_packet_error(FILE* F, picoquic_cnx_t * cnx, char const * message)
{
    picoquic_log_prefix_initial_cid64(F, picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)));
    fprintf(F, "Retry packet rejected: %s\n", message);
}

void picoquic_log_path_promotion(FILE* F, picoquic_cnx_t* cnx, int path_index, uint64_t current_time)
{
    uint64_t cnx_id64 = picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx));
    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    fprintf(F, "Path %d promoted to default at T=", path_index);
    picoquic_log_time(F, cnx, current_time, "", "\n");
    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    fprintf(F, "    Local address:");
    picoquic_log_address(F, (struct sockaddr*)& cnx->path[path_index]->local_addr);
    fprintf(F, "\n");
    picoquic_log_prefix_initial_cid64(F, cnx_id64);
    fprintf(F, "    Peer address:");
    picoquic_log_address(F, (struct sockaddr*) & cnx->path[path_index]->peer_addr);
    fprintf(F, "\n");
}

/* Adding here a declaration of binlog message defined in logwriter.c,
 * so the call the log_app_message writes on both log file and binlog */
void picoquic_binlog_message_v(picoquic_cnx_t* cnx, const char* fmt, va_list vargs);

void picoquic_txtlog_message_v(picoquic_quic_t* quic, const picoquic_connection_id_t* cid, const char* fmt, va_list vargs)
{
    FILE* F = quic->F_log;
    picoquic_log_prefix_initial_cid64(F, picoquic_val64_connection_id(*cid));

#ifdef _WINDOWS
    (void)vfprintf_s(F, fmt, vargs);
#else
    (void)vfprintf(F, fmt, vargs);
#endif

    fputc('\n', F);
}

void txtlog_context_free_app_message(picoquic_quic_t* quic, const picoquic_connection_id_t * cid, const char* fmt, va_list vargs)
{
    if (quic->F_log != NULL) {
        picoquic_txtlog_message_v(quic, cid, fmt, vargs);
    }
}

void textlog_app_message(picoquic_cnx_t* cnx, const char* fmt, va_list vargs)
{
    if (cnx->quic->F_log != NULL) {
        picoquic_txtlog_message_v(cnx->quic, &cnx->initial_cnxid, fmt, vargs);
    }
}

void textlog_quic_pdu(picoquic_quic_t* quic, int receiving, uint64_t current_time,
    uint64_t cid64,
    const struct sockaddr* addr_peer, const struct sockaddr* addr_local, size_t packet_length)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(addr_local);
#endif
    if (quic->F_log != NULL) {
        picoquic_log_packet_address(quic->F_log, cid64,
            NULL, addr_peer, receiving, packet_length, current_time);
    }
}

void textlog_pdu_ex(picoquic_cnx_t* cnx, int receiving, uint64_t current_time,
    const struct sockaddr* addr_peer, const struct sockaddr* addr_local, size_t packet_length)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(addr_local);
#endif
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        picoquic_log_packet_address(cnx->quic->F_log,
            picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)),
            cnx, addr_peer, receiving, packet_length, current_time);
    }
}

void textlog_packet(picoquic_cnx_t* cnx, int receiving, uint64_t current_time,
    picoquic_packet_header* ph, const uint8_t* bytes, size_t bytes_max)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        picoquic_log_decrypted_segment(cnx->quic->F_log, 1, 
            cnx, receiving, ph, bytes, bytes_max, 0);
    }
}

void textlog_dropped_packet(picoquic_cnx_t* cnx, picoquic_packet_header* ph,
    size_t packet_size, int ret, uint8_t* raw_data, uint64_t current_time)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        picoquic_log_decrypted_segment(cnx->quic->F_log, 1, cnx, 1, ph, raw_data, packet_size, ret);
    }
}

void textlog_buffered_packet(picoquic_cnx_t* cnx,
    picoquic_packet_type_enum ptype, uint64_t current_time)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        FILE* F = cnx->quic->F_log;

        fprintf(F, "%" PRIx64 ": ", picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)));
        picoquic_log_time(F, cnx, current_time, "T= ", ", ");
        fprintf(F, "Keys unavailable, buffered packet type %d.\n", ptype);
    }
}

void textlog_outgoing_packet(picoquic_cnx_t* cnx,
    uint8_t* bytes, uint64_t sequence_number, size_t pn_length, size_t length,
    uint8_t* send_buffer, size_t send_length, uint64_t current_time)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        picoquic_log_outgoing_segment(cnx->quic->F_log, 1,
            cnx, bytes, sequence_number, length, send_buffer, send_length, pn_length);
    }
}

void textlog_packet_lost(picoquic_cnx_t* cnx,
    picoquic_packet_type_enum ptype, uint64_t sequence_number, char const* trigger,
    picoquic_connection_id_t* dcid, size_t packet_size,
    uint64_t current_time)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        FILE* F = cnx->quic->F_log;

        fprintf(F, "%" PRIx64 ": ", picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx)));
        picoquic_log_time(F, cnx, current_time, "T= ", ", ");
        fprintf(F, "Lost packet type %d, number %" PRIu64 ", size %zu", ptype, sequence_number, packet_size);
        if (dcid != NULL) {
            fprintf(F, ", DCID ");
            picoquic_log_connection_id(F, dcid);
        }
        fprintf(F, ", reason: %s\n", trigger);
    }
}

void textlog_negotiated_alpn(picoquic_cnx_t* cnx, int is_local,
    uint8_t const* sni, size_t sni_len, uint8_t const* alpn, size_t alpn_len,
    const ptls_iovec_t* alpn_list, size_t alpn_count)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(sni);
    UNREFERENCED_PARAMETER(sni_len);
    UNREFERENCED_PARAMETER(alpn);
    UNREFERENCED_PARAMETER(alpn_len);
    UNREFERENCED_PARAMETER(alpn_list);
    UNREFERENCED_PARAMETER(alpn_count);
#endif
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        /* TODO: alpn */
        picoquic_textlog_negotiated_alpn(cnx->quic->F_log, cnx, 
            (is_local) ? 0 : 1, 1, alpn_list, alpn_count);
    }
}


void textlog_transport_extension(picoquic_cnx_t* cnx, int is_local,
    size_t param_length, uint8_t* params)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        /* TODO: alpn */
        picoquic_textlog_transport_extension(cnx->quic->F_log, cnx, (is_local)?0:1, 1, params, param_length);
    }
}

void textlog_tls_ticket(picoquic_cnx_t* cnx, uint8_t* ticket, uint16_t ticket_length)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        picoquic_log_picotls_ticket(cnx->quic->F_log, picoquic_get_logging_cnxid(cnx),
            ticket, ticket_length);
    }
}

void textlog_new_connection(picoquic_cnx_t* cnx)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(cnx);
#endif
}

void textlog_close_connection(picoquic_cnx_t* cnx)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(cnx);
#endif
}

void textlog_cc_dump(picoquic_cnx_t* cnx, uint64_t current_time)
{
    if (cnx->quic->F_log != NULL && picoquic_cnx_is_still_logging(cnx)) {
        picoquic_log_congestion_state(cnx->quic->F_log, cnx, current_time);
    }
}

struct st_picoquic_unified_logging_t textlog_functions = {
    /* Per context log function */
    txtlog_context_free_app_message,
    textlog_quic_pdu,
    /* Per connection functions */
    textlog_app_message,
    textlog_pdu_ex,
    textlog_packet,
    textlog_dropped_packet,
    textlog_buffered_packet,
    textlog_outgoing_packet,
    textlog_packet_lost,
    textlog_negotiated_alpn,
    textlog_transport_extension,
    textlog_tls_ticket,
    textlog_new_connection,
    textlog_close_connection,
    textlog_cc_dump
};

int picoquic_set_textlog(picoquic_quic_t* quic, char const* textlog_file)
{
    int ret = 0;
    FILE* F_log;

    if (quic->F_log != NULL && quic->should_close_log) {
        (void)picoquic_file_close(quic->F_log);
        quic->F_log = NULL;
    }

    if (textlog_file != NULL) {
        F_log = picoquic_file_open(textlog_file, "w");
        if (F_log == NULL) {
            DBG_PRINTF("Cannot create log file <%s>\n", textlog_file);
            ret = -1;
        }
        else {
            quic->F_log = F_log;
            quic->should_close_log = 1;
        }

        quic->text_log_fns = &textlog_functions;
    }

    return ret;
}