/*
 * ngtcp2
 *
 * Copyright (c) 2017 ngtcp2 contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "debug.h"

#include <unistd.h>

#include <cassert>
#include <random>
#include <iostream>
#include <array>

#include "util.h"

using namespace std::literals;

namespace ngtcp2 {

namespace debug {

namespace {
auto randgen = util::make_mt19937();
} // namespace

namespace {
auto *outfile = stderr;
} // namespace

int handshake_completed(ngtcp2_conn *conn, void *user_data) {
  fprintf(outfile, "QUIC handshake has completed\n");
  return 0;
}

int handshake_confirmed(ngtcp2_conn *conn, void *user_data) {
  fprintf(outfile, "QUIC handshake has been confirmed\n");
  return 0;
}

bool packet_lost(double prob) {
  auto p = std::uniform_real_distribution<>(0, 1)(randgen);
  return p < prob;
}

void print_crypto_data(ngtcp2_encryption_level encryption_level,
                       const uint8_t *data, size_t datalen) {
  const char *encryption_level_str;
  switch (encryption_level) {
  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
    encryption_level_str = "Initial";
    break;
  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
    encryption_level_str = "Handshake";
    break;
  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
    encryption_level_str = "1-RTT";
    break;
  default:
    assert(0);
    abort();
  }
  fprintf(outfile, "Ordered CRYPTO data in %s crypto level\n",
          encryption_level_str);
  util::hexdump(outfile, data, datalen);
}

void print_stream_data(int64_t stream_id, const uint8_t *data, size_t datalen) {
  fprintf(outfile, "Ordered STREAM data stream_id=0x%" PRIx64 "\n", stream_id);
  util::hexdump(outfile, data, datalen);
}

void print_initial_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "initial_secret=%s\n", util::format_hex(data, len).c_str());
}

void print_client_in_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "client_in_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_server_in_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "server_in_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_handshake_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "handshake_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_client_hs_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "client_hs_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_server_hs_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "server_hs_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_client_0rtt_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "client_0rtt_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_client_1rtt_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "client_1rtt_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_server_1rtt_secret(const uint8_t *data, size_t len) {
  fprintf(outfile, "server_1rtt_secret=%s\n",
          util::format_hex(data, len).c_str());
}

void print_client_pp_key(const uint8_t *data, size_t len) {
  fprintf(outfile, "+ client_pp_key=%s\n", util::format_hex(data, len).c_str());
}

void print_server_pp_key(const uint8_t *data, size_t len) {
  fprintf(outfile, "+ server_pp_key=%s\n", util::format_hex(data, len).c_str());
}

void print_client_pp_iv(const uint8_t *data, size_t len) {
  fprintf(outfile, "+ client_pp_iv=%s\n", util::format_hex(data, len).c_str());
}

void print_server_pp_iv(const uint8_t *data, size_t len) {
  fprintf(outfile, "+ server_pp_iv=%s\n", util::format_hex(data, len).c_str());
}

void print_client_pp_hp(const uint8_t *data, size_t len) {
  fprintf(outfile, "+ client_pp_hp=%s\n", util::format_hex(data, len).c_str());
}

void print_server_pp_hp(const uint8_t *data, size_t len) {
  fprintf(outfile, "+ server_pp_hp=%s\n", util::format_hex(data, len).c_str());
}

void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key,
                   size_t keylen, const uint8_t *iv, size_t ivlen,
                   const uint8_t *hp, size_t hplen) {
  std::cerr << "+ secret=" << util::format_hex(secret, secretlen) << "\n"
            << "+ key=" << util::format_hex(key, keylen) << "\n"
            << "+ iv=" << util::format_hex(iv, ivlen) << "\n"
            << "+ hp=" << util::format_hex(hp, hplen) << std::endl;
}

void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key,
                   size_t keylen, const uint8_t *iv, size_t ivlen) {
  std::cerr << "+ secret=" << util::format_hex(secret, secretlen) << "\n"
            << "+ key=" << util::format_hex(key, keylen) << "\n"
            << "+ iv=" << util::format_hex(iv, ivlen) << std::endl;
}

void print_hp_mask(const uint8_t *mask, size_t masklen, const uint8_t *sample,
                   size_t samplelen) {
  fprintf(outfile, "mask=%s sample=%s\n",
          util::format_hex(mask, masklen).c_str(),
          util::format_hex(sample, samplelen).c_str());
}

void log_printf(void *user_data, const char *fmt, ...) {
  va_list ap;
  std::array<char, 4096> buf;

  va_start(ap, fmt);
  auto n = vsnprintf(buf.data(), buf.size(), fmt, ap);
  va_end(ap);

  if (static_cast<size_t>(n) >= buf.size()) {
    n = buf.size() - 1;
  }

  buf[n++] = '\n';

  while (write(fileno(stderr), buf.data(), n) == -1 && errno == EINTR)
    ;
}

void path_validation(const ngtcp2_path *path,
                     ngtcp2_path_validation_result res) {
  auto local_addr = util::straddr(
      reinterpret_cast<sockaddr *>(path->local.addr), path->local.addrlen);
  auto remote_addr = util::straddr(
      reinterpret_cast<sockaddr *>(path->remote.addr), path->remote.addrlen);

  std::cerr << "Path validation against path {local:" << local_addr
            << ", remote:" << remote_addr << "} "
            << (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ? "succeeded"
                                                             : "failed")
            << std::endl;
}

void print_http_begin_request_headers(int64_t stream_id) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " request headers started\n",
          stream_id);
}

void print_http_begin_response_headers(int64_t stream_id) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " response headers started\n",
          stream_id);
}

namespace {
void print_header(const uint8_t *name, size_t namelen, const uint8_t *value,
                  size_t valuelen, uint8_t flags) {
  fprintf(outfile, "[%.*s: %.*s]%s\n", static_cast<int>(namelen), name,
          static_cast<int>(valuelen), value,
          (flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? "(sensitive)" : "");
}
} // namespace

namespace {
void print_header(const nghttp3_rcbuf *name, const nghttp3_rcbuf *value,
                  uint8_t flags) {
  auto namebuf = nghttp3_rcbuf_get_buf(name);
  auto valuebuf = nghttp3_rcbuf_get_buf(value);
  print_header(namebuf.base, namebuf.len, valuebuf.base, valuebuf.len, flags);
}
} // namespace

namespace {
void print_header(const nghttp3_nv &nv) {
  print_header(nv.name, nv.namelen, nv.value, nv.valuelen, nv.flags);
}
} // namespace

void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name,
                       const nghttp3_rcbuf *value, uint8_t flags) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " ", stream_id);
  print_header(name, value, flags);
}

void print_http_end_headers(int64_t stream_id) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " headers ended\n", stream_id);
}

void print_http_data(int64_t stream_id, const uint8_t *data, size_t datalen) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " body %zu bytes\n", stream_id,
          datalen);
  util::hexdump(outfile, data, datalen);
}

void print_http_begin_trailers(int64_t stream_id) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " trailers started\n", stream_id);
}

void print_http_end_trailers(int64_t stream_id) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " trailers ended\n", stream_id);
}

void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva,
                                size_t nvlen) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " submit request headers\n",
          stream_id);
  for (size_t i = 0; i < nvlen; ++i) {
    auto &nv = nva[i];
    print_header(nv);
  }
}

void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva,
                                 size_t nvlen) {
  fprintf(outfile, "http: stream 0x%" PRIx64 " submit response headers\n",
          stream_id);
  for (size_t i = 0; i < nvlen; ++i) {
    auto &nv = nva[i];
    print_header(nv);
  }
}

std::string_view secret_title(ngtcp2_encryption_level level) {
  switch (level) {
  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
    return "early_traffic"sv;
  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
    return "handshake_traffic"sv;
  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
    return "application_traffic"sv;
  default:
    assert(0);
    abort();
  }
}

} // namespace debug

} // namespace ngtcp2
