/*
* Author: Christian Huitema
* Copyright (c) 2019, 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 "picoquic_binlog.h"
#include "bytestream.h"
#include "tls_api.h"
#include "picotls.h"
#include "picoquic_unified_log.h"
#include "picoquic_binlog.h"

#define VARINT_LEN(bytes) ((size_t)1 << (((bytes)[0] & 0xC0) >> 6))

static const uint8_t* picoquic_log_fixed_skip(const uint8_t* bytes, const uint8_t* bytes_max, size_t size)
{
    return bytes == NULL ? NULL : ((bytes += size) <= bytes_max ? bytes : NULL);
}

static const uint8_t* picoquic_log_varint_skip(const uint8_t* bytes, const uint8_t* bytes_max)
{
    return bytes == NULL ? NULL : (bytes < bytes_max ? picoquic_log_fixed_skip(bytes, bytes_max, VARINT_LEN(bytes)) : NULL);
}

static const uint8_t* picoquic_log_varint(const uint8_t* bytes, const uint8_t* bytes_max, uint64_t* n64)
{
    size_t len = (bytes == NULL) ? 0 : picoquic_varint_decode(bytes, bytes_max - bytes, n64);
    return len == 0 ? NULL : bytes + len;
}

static const uint8_t* picoquic_log_length(const uint8_t* bytes, const uint8_t* bytes_max, size_t* nsz)
{
    uint64_t n64 = 0;
    size_t len = (bytes == NULL) ? 0 : picoquic_varint_decode(bytes, bytes_max - bytes, &n64);
    *nsz = (size_t)n64;
    return len == 0 || *nsz != n64 ? NULL : bytes + len;
}

static void picoquic_binlog_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    if (bytes != NULL && bytes_max != NULL) {
        size_t len = bytes_max - bytes;
        uint8_t varlen[8];
        size_t l_varlen = picoquic_varint_encode(varlen, 8, len);
        fwrite(varlen, 1, l_varlen, f);
        fwrite(bytes, 1, len, f);
    }
}

static const uint8_t* picoquic_log_stream_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    uint8_t ftype = bytes[0];
    size_t length = 0;
    uint8_t log_buffer[256];
    int has_length = 0;
    size_t extra_bytes = 8;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1); /* type */
    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* stream */

    if ((ftype & 4) != 0) {
        bytes = picoquic_log_varint_skip(bytes, bytes_max); /* offset */
    }

    if ((ftype & 2) != 0) {
        bytes = picoquic_log_length(bytes, bytes_max, &length); /* length */
        has_length = 1;
    } else {
        length = bytes_max - bytes;
    }

    if (length < extra_bytes) {
        /* Add up to 8 bytes of content that can be documented in the qlog */
        extra_bytes = length;
    }

    if (has_length) {
        picoquic_binlog_frame(f, bytes_begin, bytes + extra_bytes);
    }
    else {
        uint8_t* log_next = log_buffer;
        size_t l_head = bytes - bytes_begin;

        memcpy(log_buffer, bytes_begin, l_head);
        log_next += l_head;
        if ((log_next = picoquic_frames_varint_encode(log_next, log_buffer + 256, length)) != NULL) {
            memcpy(log_next, bytes, extra_bytes);
            log_next += extra_bytes;
            picoquic_binlog_frame(f, log_buffer, log_next);
        }
        else {
            picoquic_binlog_frame(f, log_buffer, log_buffer + l_head);
        }
    }

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, length);
    return bytes;
}

static const uint8_t* picoquic_log_ack_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    uint8_t ftype = bytes[0];
    uint64_t nb_blocks;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);

    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint(bytes, bytes_max, &nb_blocks);

    for (uint64_t i = 0; bytes != NULL && i <= nb_blocks; i++) {
        if (i != 0) {
            bytes = picoquic_log_varint_skip(bytes, bytes_max);
        }
        bytes = picoquic_log_varint_skip(bytes, bytes_max);
    }
    
    if (ftype == picoquic_frame_type_ack_ecn) {
        bytes = picoquic_log_varint_skip(bytes, bytes_max);
        bytes = picoquic_log_varint_skip(bytes, bytes_max);
        bytes = picoquic_log_varint_skip(bytes, bytes_max);
    }

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_reset_stream_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t * bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_stop_sending_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_close_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    size_t length = 0;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_length(bytes, bytes_max, &length);
    bytes = picoquic_log_fixed_skip(bytes, bytes_max, length);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_app_close_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    size_t length = 0;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_length(bytes, bytes_max, &length);
    bytes = picoquic_log_fixed_skip(bytes, bytes_max, length);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_max_data_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_max_stream_data_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_max_stream_id_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_blocked_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_stream_blocked_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_streams_blocked_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_new_connection_id_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    if (bytes != NULL) {
        bytes = picoquic_log_fixed_skip(bytes, bytes_max, ((size_t)1) + bytes[0]);
    }

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, PICOQUIC_RESET_SECRET_SIZE);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_retire_connection_id_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_new_token_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    size_t length = 0;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_length(bytes, bytes_max, &length);

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, length);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_path_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1 + 8);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_crypto_hs_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    size_t length = 0;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);
    bytes = picoquic_log_varint_skip(bytes, bytes_max);
    bytes = picoquic_log_length(bytes, bytes_max, &length);

    picoquic_binlog_frame(f, bytes_begin, bytes);

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, length);
    return bytes;
}


static const uint8_t* picoquic_log_handshake_done_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);

    picoquic_binlog_frame(f, bytes_begin, bytes);
    return bytes;
}

static const uint8_t* picoquic_log_datagram_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;
    uint8_t ftype = bytes[0];
    size_t length = 0;

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, 1);

    if (ftype & 1) {
        bytes = picoquic_log_length(bytes, bytes_max, &length);
    } else {
        length = bytes_max - bytes;
    }

    picoquic_binlog_frame(f, bytes_begin, bytes);

    bytes = picoquic_log_fixed_skip(bytes, bytes_max, length);
    return bytes;
}

static const uint8_t* picoquic_log_time_stamp_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* frame type as varint */
    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* time stamp as varint */

    picoquic_binlog_frame(f, bytes_begin, bytes);

    return bytes;
}

static const uint8_t* picoquic_log_ack_frequency_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    const uint8_t* bytes_begin = bytes;

    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* frame type as varint */
    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* Seq num */
    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* Packet tolerance */
    bytes = picoquic_log_varint_skip(bytes, bytes_max); /* Max ACK delay */

    picoquic_binlog_frame(f, bytes_begin, bytes);

    return bytes;
}

static const uint8_t* picoquic_log_erroring_frame(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    size_t frame_size = bytes_max - bytes;
    size_t copied = (frame_size > 8) ? 8 : frame_size;

    picoquic_binlog_frame(f, bytes, bytes + copied);

    return NULL;
}

static const uint8_t* picoquic_log_padding(FILE* f, const uint8_t* bytes, const uint8_t* bytes_max)
{
    picoquic_binlog_frame(f, bytes, bytes + 1);

    uint8_t ftype = bytes[0];
    while (bytes < bytes_max && bytes[0] == ftype) {
        bytes++;
    }

    return bytes;
}

void picoquic_binlog_frames(FILE * f, const uint8_t* bytes, size_t length)
{
    const uint8_t* bytes_max = bytes + length;

    while (bytes != NULL && bytes < bytes_max) {
        uint64_t ftype= 0;
        size_t ftype_ll = picoquic_varint_decode(bytes, length, &ftype);

        if (ftype_ll == 0) {
            /* Error, incorrect frame type encoding */
            bytes = NULL;
            break;
        }
        else if (ftype < 64 && ftype_ll != 1) {
            /* Error, incorrect frame type encoding */
            bytes = NULL;
            break;
        }

        if (PICOQUIC_IN_RANGE(ftype, picoquic_frame_type_stream_range_min, picoquic_frame_type_stream_range_max)) {
            bytes = picoquic_log_stream_frame(f, bytes, bytes_max);
            continue;
        }

        switch (ftype) {
        case picoquic_frame_type_ack:
        case picoquic_frame_type_ack_ecn:
            bytes = picoquic_log_ack_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_retire_connection_id:
            bytes = picoquic_log_retire_connection_id_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_padding:
        case picoquic_frame_type_ping:
            bytes = picoquic_log_padding(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_reset_stream:
            bytes = picoquic_log_reset_stream_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_connection_close:
            bytes = picoquic_log_close_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_application_close:
            bytes = picoquic_log_app_close_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_max_data:
            bytes = picoquic_log_max_data_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_max_stream_data:
            bytes = picoquic_log_max_stream_data_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_max_streams_bidir:
        case picoquic_frame_type_max_streams_unidir:
            bytes = picoquic_log_max_stream_id_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_data_blocked:
            bytes = picoquic_log_blocked_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_stream_data_blocked:
            bytes = picoquic_log_stream_blocked_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_streams_blocked_bidir:
        case picoquic_frame_type_streams_blocked_unidir:
            bytes = picoquic_log_streams_blocked_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_new_connection_id:
            bytes = picoquic_log_new_connection_id_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_stop_sending:
            bytes = picoquic_log_stop_sending_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_path_challenge:
        case picoquic_frame_type_path_response:
            bytes = picoquic_log_path_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_crypto_hs:
            bytes = picoquic_log_crypto_hs_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_new_token:
            bytes = picoquic_log_new_token_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_handshake_done:
            bytes = picoquic_log_handshake_done_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_datagram:
        case picoquic_frame_type_datagram_l:
            bytes = picoquic_log_datagram_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_ack_frequency:
            bytes = picoquic_log_ack_frequency_frame(f, bytes, bytes_max);
            break;
        case picoquic_frame_type_time_stamp:
            bytes = picoquic_log_time_stamp_frame(f, bytes, bytes_max);
            break;
        default:
            bytes = picoquic_log_erroring_frame(f, bytes, bytes_max);
            break;
        }
    }
}

void binlog_pdu(FILE* f, const picoquic_connection_id_t* cid, int receiving, uint64_t current_time,
    const struct sockaddr* addr_peer, const struct sockaddr* addr_local, size_t packet_length)
{
    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);

    /* Common chunk header */
    bytewrite_cid(msg, cid);
    bytewrite_vint(msg, current_time);
    bytewrite_vint(msg, ((uint64_t)picoquic_log_event_pdu_sent) + receiving);

    /* PDU information */
    bytewrite_addr(msg, addr_peer);
    bytewrite_vint(msg, packet_length);
    bytewrite_addr(msg, addr_local);

    uint8_t head[4] = { 0 };
    picoformat_32(head, (uint32_t)bytestream_length(msg));

    (void)fwrite(head, sizeof(head), 1, f);
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}

static void binlog_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)
{
    if (cnx != NULL && cnx->f_binlog != NULL && picoquic_cnx_is_still_logging(cnx)) {
        binlog_pdu(cnx->f_binlog, &cnx->initial_cnxid, receiving, current_time, addr_peer, addr_local, packet_length);
    }
}

void binlog_packet(FILE* f, const picoquic_connection_id_t* cid, int receiving, uint64_t current_time,
    const picoquic_packet_header* ph, const uint8_t* bytes, size_t bytes_max)
{
    long fpos0 = ftell(f);

    uint8_t head[4] = { 0 };
    (void)fwrite(head, 4, 1, f);

    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);

    /* Common chunk header */
    bytewrite_cid(msg, cid);
    bytewrite_vint(msg, current_time);
    bytewrite_vint(msg, ((uint64_t)picoquic_log_event_packet_sent) + receiving);

    /* packet information */
    bytewrite_vint(msg, bytes_max);

    /* packet header */
    bytewrite_int8(msg, (uint8_t)(64*ph->quic_bit_is_zero + 2 * ph->spin + ph->key_phase));
    bytewrite_vint(msg, ph->payload_length);
    bytewrite_vint(msg, ph->ptype);
    bytewrite_vint(msg, ph->pn64);

    bytewrite_cid(msg, &ph->dest_cnx_id);
    bytewrite_cid(msg, &ph->srce_cnx_id);

    if (ph->ptype != picoquic_packet_1rtt_protected &&
        ph->ptype != picoquic_packet_version_negotiation) {
        bytewrite_int32(msg, ph->vn);
    }

    if (ph->ptype == picoquic_packet_initial) {
        bytewrite_vint(msg, ph->token_length);
        bytewrite_buffer(msg, ph->token_bytes, ph->token_length);
    }

    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);

    /* frame information */
    if (ph->ptype == picoquic_packet_version_negotiation || ph->ptype == picoquic_packet_retry) {
        picoquic_binlog_frame(f, bytes + ph->offset, bytes + bytes_max);
    }
    else if (ph->ptype != picoquic_packet_error) {
        picoquic_binlog_frames(f, bytes + ph->offset, ph->payload_length);
    }

    /* re-write chunk size field */
    long fpos1 = ftell(f);

    picoformat_32(head, (uint32_t)(fpos1 - fpos0 - 4));

    (void)fseek(f, fpos0, SEEK_SET);
    (void)fwrite(head, 4, 1, f);
    (void)fseek(f, 0, SEEK_END);
}

static void binlog_packet_ex(picoquic_cnx_t* cnx, int receiving, uint64_t current_time,
    picoquic_packet_header* ph, const uint8_t* bytes, size_t bytes_max)
{
    if (cnx != NULL && cnx->f_binlog != NULL && picoquic_cnx_is_still_logging(cnx)) {
        binlog_packet(cnx->f_binlog, &cnx->initial_cnxid, receiving, current_time, ph, bytes, bytes_max);
    }
}

void binlog_dropped_packet(picoquic_cnx_t* cnx,
    picoquic_packet_header* ph,  size_t packet_size, int err,
    uint8_t * raw_data, uint64_t current_time)
{
    FILE* f = cnx->f_binlog;
    size_t raw_size = packet_size;
    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);

    if (err == PICOQUIC_ERROR_AEAD_CHECK) {
        /* Do not log on decryption error, because the buffer was randomized by decryption */
        raw_size = 0;
    } else if (raw_size > 32) {
        raw_size = 32;
    }

    bytewrite_int32(msg, 0);
    bytewrite_cid(msg, &cnx->initial_cnxid);
    bytewrite_vint(msg, current_time);
    bytewrite_vint(msg, picoquic_log_event_packet_dropped);
    bytewrite_vint(msg, ph->ptype);
    bytewrite_vint(msg, packet_size);
    bytewrite_vint(msg, err);
    bytewrite_vint(msg, raw_size);
    (void)bytewrite_buffer(msg, raw_data, raw_size);

    /* write the frame length at the reserved spot, and save to log file*/
    picoformat_32(msg->data, (uint32_t)(msg->ptr - 4));
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}

void binlog_buffered_packet(picoquic_cnx_t* cnx,
    picoquic_packet_type_enum ptype, uint64_t current_time)
{
    FILE* f = cnx->f_binlog;
    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);

    bytewrite_int32(msg, 0);
    bytewrite_cid(msg, &cnx->initial_cnxid);
    bytewrite_vint(msg, current_time);
    bytewrite_vint(msg, picoquic_log_event_packet_buffered);
    bytewrite_vint(msg, ptype);
    (void)bytewrite_cstr(msg, "keys_unavailable");

    /* write the frame length at the reserved spot, and save to log file*/
    picoformat_32(msg->data, (uint32_t)(msg->ptr - 4));
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}


void binlog_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)
{
    FILE* f = cnx->f_binlog;

    picoquic_cnx_t* pcnx = cnx;
    picoquic_packet_header ph;
    size_t checksum_length = 16;
    struct sockaddr_in default_addr;

    const picoquic_connection_id_t * cnxid = (cnx != NULL) ? &cnx->initial_cnxid : &picoquic_null_connection_id;

    memset(&default_addr, 0, sizeof(struct sockaddr_in));
    default_addr.sin_family = AF_INET;

    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);

    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);
        }
    }

    ph.pn64 = sequence_number;
    ph.pn = (uint32_t)ph.pn64;
    if (ph.ptype != picoquic_packet_retry) {
        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;
        }
    }

    binlog_packet(f, cnxid, 0, current_time, &ph, bytes, length);
}

void binlog_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)
{
    FILE* f = cnx->f_binlog;

    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);

    bytewrite_int32(msg, 0);
    bytewrite_cid(msg, &cnx->initial_cnxid);
    bytewrite_vint(msg, current_time);
    bytewrite_vint(msg, picoquic_log_event_packet_lost);

    bytewrite_vint(msg, ptype);
    bytewrite_vint(msg, sequence_number);
    bytewrite_cstr(msg, trigger);
    if (dcid != NULL) {
        bytewrite_cid(msg, dcid);
    }
    else {
        bytewrite_int8(msg, 0);
    }
    bytewrite_vint(msg, packet_size);

    /* write the frame length at the reserved spot, and save to log file*/
    picoformat_32(msg->data, (uint32_t)(msg->ptr - 4));
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}


void binlog_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)
{
    FILE* f = cnx->f_binlog;

    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
    bytewrite_cid(msg, &cnx->initial_cnxid);
    bytewrite_vint(msg, picoquic_get_quic_time(cnx->quic));
    bytewrite_vint(msg, picoquic_log_event_alpn_update);

    bytewrite_vint(msg, is_local);
    bytewrite_vint(msg, sni_len);
    if (sni_len > 0) {
        bytewrite_buffer(msg, sni, sni_len);
    }

    bytewrite_vint(msg, alpn_count);
    if (alpn_count > 0) {
        for (size_t i = 0; i < alpn_count; i++) {
            bytewrite_vint(msg, alpn_list[i].len);
            bytewrite_buffer(msg, alpn_list[i].base, alpn_list[i].len);
        }
    }

    bytewrite_vint(msg, alpn_len);
    if (alpn_len > 0) {
        bytewrite_buffer(msg, alpn, alpn_len);
    }

    bytestream_buf stream_head;
    bytestream* head = bytestream_buf_init(&stream_head, 4);
    bytewrite_int32(head, (uint32_t)bytestream_length(msg));

    (void)fwrite(bytestream_data(head), bytestream_length(head), 1, f);
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}

void binlog_transport_extension(picoquic_cnx_t* cnx, int is_local,
    size_t param_length, uint8_t* params)
{
    FILE* f = cnx->f_binlog;

    bytestream_buf stream_msg;
    bytestream* msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
    bytewrite_cid(msg, &cnx->initial_cnxid);
    bytewrite_vint(msg, picoquic_get_quic_time(cnx->quic));
    bytewrite_vint(msg, picoquic_log_event_param_update);

    bytewrite_vint(msg, is_local);
    bytewrite_vint(msg, param_length);

    if (param_length > 0) {
        bytewrite_buffer(msg, params, param_length);
    }

    bytestream_buf stream_head;
    bytestream* head = bytestream_buf_init(&stream_head, 4);
    bytewrite_int32(head, (uint32_t)bytestream_length(msg));

    (void)fwrite(bytestream_data(head), bytestream_length(head), 1, f);
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}

void binlog_picotls_ticket(FILE* f, picoquic_connection_id_t cnx_id,
    uint8_t* ticket, uint16_t ticket_length)
{
    bytestream_buf stream_msg;
    bytestream * msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
    bytewrite_cid(msg, &cnx_id);
    bytewrite_vint(msg, 0);
    bytewrite_vint(msg, picoquic_log_event_tls_key_update);

    bytewrite_vint(msg, ticket_length);
    bytewrite_buffer(msg, ticket, ticket_length);

    bytestream_buf stream_head;
    bytestream * head = bytestream_buf_init(&stream_head, 8);
    bytewrite_int32(head, (uint32_t)bytestream_length(msg));

    (void)fwrite(bytestream_data(head), bytestream_length(head), 1, f);
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);
}

static void binlog_picotls_ticket_ex(picoquic_cnx_t* cnx,
    uint8_t* ticket, uint16_t ticket_length)
{
    if (cnx != NULL && cnx->f_binlog != NULL && picoquic_cnx_is_still_logging(cnx)) {
        binlog_picotls_ticket(cnx->f_binlog, cnx->initial_cnxid, ticket, ticket_length);
    }
}

FILE* create_binlog(char const* binlog_file, uint64_t creation_time);

void binlog_new_connection(picoquic_cnx_t * cnx)
{
    char const* bin_dir = (cnx->quic->binlog_dir == NULL) ? cnx->quic->qlog_dir : cnx->quic->binlog_dir;

    if (bin_dir == NULL) {
        return;
    }

    if (cnx->quic->current_number_of_open_logs >= cnx->quic->max_simultaneous_logs) {
        return;
    }

    int ret = 0;

    cnx->f_binlog = picoquic_file_close(cnx->f_binlog);
    
    char cid_name[2 * PICOQUIC_CONNECTION_ID_MAX_SIZE + 1];
    if (picoquic_print_connection_id_hexa(cid_name, sizeof(cid_name), &cnx->initial_cnxid) != 0) {
        ret = -1;
    }

    char log_filename[512];
    if (ret == 0) {
        if (picoquic_sprintf(log_filename, sizeof(log_filename), NULL, "%s%s%s.%s.log",
            bin_dir, PICOQUIC_FILE_SEPARATOR, cid_name,
            (cnx->client_mode)?"client":"server") != 0) {
            ret = -1;
        }
        else {
            picoquic_string_free(cnx->binlog_file_name);
            cnx->binlog_file_name = picoquic_string_duplicate(log_filename);
        }
    }

    if (ret == 0) {
        cnx->f_binlog = create_binlog(log_filename, picoquic_get_quic_time(cnx->quic));
        if (cnx->f_binlog == NULL) {
            cnx->binlog_file_name = picoquic_string_free(cnx->binlog_file_name);
            ret = -1;
        }
        else {
            cnx->quic->current_number_of_open_logs++;
        }
    }

    if (ret == 0) {
        bytestream_buf stream_msg;
        bytestream * msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
        bytewrite_cid(msg, &cnx->initial_cnxid);
        bytewrite_vint(msg, cnx->start_time);
        bytewrite_vint(msg, picoquic_log_event_new_connection);

        bytewrite_int8(msg, cnx->client_mode != 0);
        bytewrite_int32(msg, cnx->proposed_version);
        bytewrite_cid(msg, &cnx->path[0]->remote_cnxid);

        /* Algorithms used */
        bytewrite_cstr(msg, cnx->congestion_alg->congestion_algorithm_id);
        bytewrite_vint(msg, cnx->spin_policy);

        bytestream_buf stream_head;
        bytestream * head = bytestream_buf_init(&stream_head, 8);
        bytewrite_int32(head, (uint32_t)bytestream_length(msg));

        (void)fwrite(bytestream_data(head), bytestream_length(head), 1, cnx->f_binlog);
        (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, cnx->f_binlog);
    }
}

void binlog_close_connection(picoquic_cnx_t * cnx)
{
    FILE * f = cnx->f_binlog;
    if (f == NULL) {
        return;
    }

    bytestream_buf stream_msg;
    bytestream * msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
    bytewrite_cid(msg, &cnx->initial_cnxid);
    bytewrite_vint(msg, picoquic_get_quic_time(cnx->quic));
    bytewrite_vint(msg, picoquic_log_event_connection_close);

    bytestream_buf stream_head;
    bytestream * head = bytestream_buf_init(&stream_head, 8);
    bytewrite_int32(head, (uint32_t)bytestream_length(msg));

    (void)fwrite(bytestream_data(head), bytestream_length(head), 1, f);
    (void)fwrite(bytestream_data(msg), bytestream_length(msg), 1, f);

    fflush(f);

    cnx->f_binlog = picoquic_file_close(cnx->f_binlog);

    if (cnx->quic->qlog_dir != NULL && cnx->quic->autoqlog_fn != NULL) {
        (void)cnx->quic->autoqlog_fn(cnx);
    }
    cnx->binlog_file_name = picoquic_string_free(cnx->binlog_file_name);
    if (cnx->quic->current_number_of_open_logs > 0) {
        cnx->quic->current_number_of_open_logs--;
    }
}

FILE* create_binlog(char const* binlog_file, uint64_t creation_time)
{
    FILE* f_binlog = picoquic_file_open(binlog_file, "wb");
    if (f_binlog == NULL) {
        DBG_PRINTF("Cannot open file %s for write.\n", binlog_file);
    }
    else {
        /* Write a header text with version identifier and current date  */
        bytestream_buf stream;
        bytestream* ps = bytestream_buf_init(&stream, 16);
        bytewrite_int32(ps, FOURCC('q', 'l', 'o', 'g'));
        bytewrite_int32(ps, 0x01);
        bytewrite_int64(ps, creation_time);

        if (fwrite(bytestream_data(ps), bytestream_length(ps), 1, f_binlog) <= 0) {
            DBG_PRINTF("Cannot write header for file %s.\n", binlog_file);
            f_binlog = picoquic_file_close(f_binlog);
        }
    }

    return f_binlog;
}

/*
 * Log the state of the congestion management, retransmission, etc.
 * Call either just after processing a received packet, or just after
 * sending a packet.
 */

void binlog_cc_dump(picoquic_cnx_t* cnx, uint64_t current_time)
{
    if (cnx->f_binlog == NULL) {
        return;
    }

    bytestream_buf stream_msg;
    bytestream* ps_msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
    picoquic_packet_context_t* pkt_ctx = &cnx->pkt_ctx[picoquic_packet_context_application];
    picoquic_path_t* path = cnx->path[0];

    bytewrite_cid(ps_msg, &cnx->initial_cnxid);
    bytewrite_vint(ps_msg, current_time);
    bytewrite_vint(ps_msg, picoquic_log_event_cc_update);

    bytewrite_vint(ps_msg, cnx->pkt_ctx[picoquic_packet_context_application].send_sequence);

    if (pkt_ctx->highest_acknowledged != (uint64_t)(int64_t)-1) {
        bytewrite_vint(ps_msg, 1);
        bytewrite_vint(ps_msg, pkt_ctx->highest_acknowledged);
        bytewrite_vint(ps_msg, pkt_ctx->highest_acknowledged_time - cnx->start_time);
        bytewrite_vint(ps_msg, pkt_ctx->latest_time_acknowledged - cnx->start_time);
    }
    else {
        bytewrite_vint(ps_msg, 0);
    }

    bytewrite_vint(ps_msg, path->cwin);
    bytewrite_vint(ps_msg, path->one_way_delay_sample);
    bytewrite_vint(ps_msg, path->rtt_sample);
    bytewrite_vint(ps_msg, path->smoothed_rtt);
    bytewrite_vint(ps_msg, path->rtt_min);
    bytewrite_vint(ps_msg, path->bandwidth_estimate);
    bytewrite_vint(ps_msg, path->receive_rate_estimate);
    bytewrite_vint(ps_msg, path->send_mtu);
    bytewrite_vint(ps_msg, path->pacing_packet_time_microsec);
    bytewrite_vint(ps_msg, cnx->nb_retransmission_total);
    bytewrite_vint(ps_msg, cnx->nb_spurious);
    bytewrite_vint(ps_msg, cnx->cwin_blocked);
    bytewrite_vint(ps_msg, cnx->flow_blocked);
    bytewrite_vint(ps_msg, cnx->stream_blocked);

    if (cnx->congestion_alg == NULL) {
        bytewrite_vint(ps_msg, 0);
        bytewrite_vint(ps_msg, 0);
    }
    else {
        uint64_t cc_state;
        uint64_t cc_param;

        cnx->congestion_alg->alg_observe(cnx->path[0], &cc_state, &cc_param);
        bytewrite_vint(ps_msg, cc_state);
        bytewrite_vint(ps_msg, cc_param);
    }

    bytewrite_vint(ps_msg, path->max_bandwidth_estimate);
    bytewrite_vint(ps_msg, path->bytes_in_transit);

    bytestream_buf stream_head;
    bytestream* ps_head = bytestream_buf_init(&stream_head, BYTESTREAM_MAX_BUFFER_SIZE);

    bytewrite_int32(ps_head, (uint32_t)bytestream_length(ps_msg));

    (void)fwrite(bytestream_data(ps_head), bytestream_length(ps_head), 1, cnx->f_binlog);
    (void)fwrite(bytestream_data(ps_msg), bytestream_length(ps_msg), 1, cnx->f_binlog);
}

/*
 * Write an information message frame, for free form debugging.
 */

void picoquic_binlog_message_v(picoquic_cnx_t* cnx, const char* fmt, va_list vargs)
{
    if (cnx->f_binlog == NULL) {
        return;
    }
    bytestream_buf stream_msg;
    bytestream* ps_msg = bytestream_buf_init(&stream_msg, BYTESTREAM_MAX_BUFFER_SIZE);
    size_t message_len;
    char* message_text;
    int written = -1;

    bytewrite_cid(ps_msg, &cnx->initial_cnxid);
    bytewrite_vint(ps_msg, picoquic_get_quic_time(cnx->quic));
    bytewrite_vint(ps_msg, picoquic_log_event_info_message);
    message_text = (char*)(ps_msg->data + ps_msg->ptr);
#ifdef _WINDOWS
    written = vsnprintf_s(message_text,
        ps_msg->size - ps_msg->ptr, _TRUNCATE, fmt, vargs);
    message_len = (written < 0) ? ps_msg->size - ps_msg->ptr - 1 : written;
#else
    written = vsnprintf(message_text, ps_msg->size - ps_msg->ptr, fmt, vargs);
    if (written < 0 || written >= ps_msg->size - ps_msg->ptr){
        message_len = ps_msg->size - ps_msg->ptr - 1;
    } else {
        message_len = written;
    }
#endif
    ps_msg->ptr += message_len;

    bytestream_buf stream_head;
    bytestream* ps_head = bytestream_buf_init(&stream_head, BYTESTREAM_MAX_BUFFER_SIZE);

    bytewrite_int32(ps_head, (uint32_t)bytestream_length(ps_msg));

    (void)fwrite(bytestream_data(ps_head), bytestream_length(ps_head), 1, cnx->f_binlog);
    (void)fwrite(bytestream_data(ps_msg), bytestream_length(ps_msg), 1, cnx->f_binlog);
}

/* Log an event that cannot be attached to a specific connection */
void binlog_ignore_quic_app_message(picoquic_quic_t* quic, const picoquic_connection_id_t* cid, const char* fmt, va_list vargs)
{
#ifdef _WINDOWS
    UNREFERENCED_PARAMETER(quic);
    UNREFERENCED_PARAMETER(cid);
    UNREFERENCED_PARAMETER(fmt);
#endif
}

/* Log arrival or departure of an UDP datagram for an unknown connection */
void binlog_ignore_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(quic);
    UNREFERENCED_PARAMETER(receiving);
    UNREFERENCED_PARAMETER(current_time);
    UNREFERENCED_PARAMETER(addr_peer);
    UNREFERENCED_PARAMETER(addr_local);
    UNREFERENCED_PARAMETER(packet_length);
#endif
}

/* Log an event relating to a specific connection */
static void binlog_app_message(picoquic_cnx_t* cnx, const char* fmt, va_list vargs)
{
    if (cnx->f_binlog != NULL) {
        picoquic_binlog_message_v(cnx, fmt, vargs);
    }
}

struct st_picoquic_unified_logging_t binlog_functions = {
    /* Per context log function */
    binlog_ignore_quic_app_message,
    binlog_ignore_quic_pdu,
    /* Per connection functions */
    binlog_app_message,
    binlog_pdu_ex,
    binlog_packet_ex,
    binlog_dropped_packet,
    binlog_buffered_packet,
    binlog_outgoing_packet,
    binlog_packet_lost,
    binlog_negotiated_alpn,
    binlog_transport_extension,
    binlog_picotls_ticket_ex,
    binlog_new_connection,
    binlog_close_connection,
    binlog_cc_dump
};

int picoquic_set_binlog(picoquic_quic_t* quic, char const* binlog_dir)
{
    quic->binlog_dir = picoquic_string_free(quic->binlog_dir);
    quic->binlog_dir = picoquic_string_duplicate(binlog_dir);
    quic->bin_log_fns = &binlog_functions;
    return 0;
}

void picoquic_enable_binlog(picoquic_quic_t* quic)
{
    quic->bin_log_fns = &binlog_functions;
}
