/*
 * 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 <chrono>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <memory>
#include <fstream>
#include <iomanip>

#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <netinet/udp.h>
#include <net/if.h>

#include <http-parser/http_parser.h>

#include "h09server.h"
#include "network.h"
#include "debug.h"
#include "util.h"
#include "shared.h"
#include "http.h"
#include "template.h"

using namespace ngtcp2;
using namespace std::literals;

#ifndef NGTCP2_ENABLE_UDP_GSO
#  ifdef UDP_SEGMENT
#    define NGTCP2_ENABLE_UDP_GSO 1
#  else // !UDP_SEGMENT
#    define NGTCP2_ENABLE_UDP_GSO 0
#  endif // !UDP_SEGMENT
#endif   // NGTCP2_ENABLE_UDP_GSO

namespace {
constexpr size_t NGTCP2_SV_SCIDLEN = 18;
} // namespace

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

Config config{};

Stream::Stream(int64_t stream_id, Handler *handler)
    : stream_id(stream_id), handler(handler), eos(false) {
  nghttp3_buf_init(&respbuf);
  htp.data = this;
  http_parser_init(&htp, HTTP_REQUEST);
}

namespace {
constexpr char NGTCP2_SERVER[] = "ngtcp2 server";
} // namespace

namespace {
std::string make_status_body(unsigned int status_code) {
  auto status_string = std::to_string(status_code);
  auto reason_phrase = http::get_reason_phrase(status_code);

  std::string body;
  body = "<html><head><title>";
  body += status_string;
  body += ' ';
  body += reason_phrase;
  body += "</title></head><body><h1>";
  body += status_string;
  body += ' ';
  body += reason_phrase;
  body += "</h1><hr><address>";
  body += NGTCP2_SERVER;
  body += " at port ";
  body += std::to_string(config.port);
  body += "</address>";
  body += "</body></html>";
  return body;
}
} // namespace

struct Request {
  std::string path;
  struct {
    int32_t urgency;
    int inc;
  } pri;
};

namespace {
Request request_path(const std::string_view &uri) {
  http_parser_url u;
  Request req;

  req.pri.urgency = -1;
  req.pri.inc = -1;

  http_parser_url_init(&u);

  if (auto rv = http_parser_parse_url(uri.data(), uri.size(),
                                      /* is_connect = */ 0, &u);
      rv != 0) {
    return req;
  }

  if (u.field_set & (1 << UF_PATH)) {
    req.path = std::string(uri.data() + u.field_data[UF_PATH].off,
                           u.field_data[UF_PATH].len);
    if (req.path.find('%') != std::string::npos) {
      req.path = util::percent_decode(std::begin(req.path), std::end(req.path));
    }
    if (!req.path.empty() && req.path.back() == '/') {
      req.path += "index.html";
    }
  } else {
    req.path = "/index.html";
  }

  req.path = util::normalize_path(req.path);
  if (req.path == "/") {
    req.path = "/index.html";
  }

  if (u.field_set & (1 << UF_QUERY)) {
    static constexpr char urgency_prefix[] = "u=";
    static constexpr char inc_prefix[] = "i=";
    auto q = std::string(uri.data() + u.field_data[UF_QUERY].off,
                         u.field_data[UF_QUERY].len);
    for (auto p = std::begin(q); p != std::end(q);) {
      if (util::istarts_with(p, std::end(q), std::begin(urgency_prefix),
                             std::end(urgency_prefix) - 1)) {
        auto urgency_start = p + sizeof(urgency_prefix) - 1;
        auto urgency_end = std::find(urgency_start, std::end(q), '&');
        if (urgency_start + 1 == urgency_end && '0' <= *urgency_start &&
            *urgency_start <= '7') {
          req.pri.urgency = *urgency_start - '0';
        }
        if (urgency_end == std::end(q)) {
          break;
        }
        p = urgency_end + 1;
        continue;
      }
      if (util::istarts_with(p, std::end(q), std::begin(inc_prefix),
                             std::end(inc_prefix) - 1)) {
        auto inc_start = p + sizeof(inc_prefix) - 1;
        auto inc_end = std::find(inc_start, std::end(q), '&');
        if (inc_start + 1 == inc_end &&
            (*inc_start == '0' || *inc_start == '1')) {
          req.pri.inc = *inc_start - '0';
        }
        if (inc_end == std::end(q)) {
          break;
        }
        p = inc_end + 1;
        continue;
      }

      p = std::find(p, std::end(q), '&');
      if (p == std::end(q)) {
        break;
      }
      ++p;
    }
  }
  return req;
}
} // namespace

enum FileEntryFlag {
  FILE_ENTRY_TYPE_DIR = 0x1,
};

struct FileEntry {
  uint64_t len;
  void *map;
  int fd;
  uint8_t flags;
};

namespace {
std::unordered_map<std::string, FileEntry> file_cache;
} // namespace

std::pair<FileEntry, int> Stream::open_file(const std::string &path) {
  auto it = file_cache.find(path);
  if (it != std::end(file_cache)) {
    return {(*it).second, 0};
  }

  auto fd = open(path.c_str(), O_RDONLY);
  if (fd == -1) {
    return {{}, -1};
  }

  struct stat st {};
  if (fstat(fd, &st) != 0) {
    close(fd);
    return {{}, -1};
  }

  FileEntry fe{};
  if (st.st_mode & S_IFDIR) {
    fe.flags |= FILE_ENTRY_TYPE_DIR;
    fe.fd = -1;
    close(fd);
  } else {
    fe.fd = fd;
    fe.len = st.st_size;
    fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0);
    if (fe.map == MAP_FAILED) {
      std::cerr << "mmap: " << strerror(errno) << std::endl;
      close(fd);
      return {{}, -1};
    }
  }

  file_cache.emplace(path, fe);

  return {std::move(fe), 0};
}

void Stream::map_file(const FileEntry &fe) {
  respbuf.begin = respbuf.pos = static_cast<uint8_t *>(fe.map);
  respbuf.end = respbuf.last = respbuf.begin + fe.len;
}

int Stream::send_status_response(unsigned int status_code) {
  status_resp_body = make_status_body(status_code);

  respbuf.begin = respbuf.pos =
      reinterpret_cast<uint8_t *>(status_resp_body.data());
  respbuf.end = respbuf.last = respbuf.begin + status_resp_body.size();

  handler->add_sendq(this);
  handler->shutdown_read(stream_id, 0);

  return 0;
}

int Stream::start_response() {
  if (uri.empty()) {
    return send_status_response(400);
  }

  auto req = request_path(uri);
  if (req.path.empty()) {
    return send_status_response(400);
  }

  auto path = config.htdocs + req.path;
  auto [fe, rv] = open_file(path);
  if (rv != 0) {
    send_status_response(404);
    return 0;
  }

  if (fe.flags & FILE_ENTRY_TYPE_DIR) {
    send_status_response(308);
    return 0;
  }

  map_file(fe);

  if (!config.quiet) {
    std::array<nghttp3_nv, 1> nva{
        util::make_nv(":status", "200"),
    };

    debug::print_http_response_headers(stream_id, nva.data(), nva.size());
  }

  handler->add_sendq(this);

  return 0;
}

namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
  ev_io_stop(loop, w);

  auto h = static_cast<Handler *>(w->data);
  auto s = h->server();

  switch (h->on_write()) {
  case 0:
  case NETWORK_ERR_CLOSE_WAIT:
    return;
  default:
    s->remove(h);
  }
}
} // namespace

namespace {
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
  auto h = static_cast<Handler *>(w->data);
  auto s = h->server();

  if (ngtcp2_conn_is_in_closing_period(h->conn())) {
    if (!config.quiet) {
      std::cerr << "Closing Period is over" << std::endl;
    }

    s->remove(h);
    return;
  }
  if (h->draining()) {
    if (!config.quiet) {
      std::cerr << "Draining Period is over" << std::endl;
    }

    s->remove(h);
    return;
  }

  if (!config.quiet) {
    std::cerr << "Timeout" << std::endl;
  }

  h->start_draining_period();
}
} // namespace

namespace {
void retransmitcb(struct ev_loop *loop, ev_timer *w, int revents) {
  int rv;

  auto h = static_cast<Handler *>(w->data);
  auto s = h->server();

  if (!config.quiet) {
    std::cerr << "Timer expired" << std::endl;
  }

  rv = h->handle_expiry();
  if (rv != 0) {
    goto fail;
  }

  rv = h->on_write();
  if (rv != 0) {
    goto fail;
  }

  return;

fail:
  switch (rv) {
  case NETWORK_ERR_CLOSE_WAIT:
    ev_timer_stop(loop, w);
    return;
  default:
    s->remove(h);
    return;
  }
}
} // namespace

Handler::Handler(struct ev_loop *loop, Server *server)
    : loop_(loop),
      server_(server),
      qlog_(nullptr),
      scid_{},
      nkey_update_(0),
      draining_(false) {
  ev_io_init(&wev_, writecb, 0, EV_WRITE);
  wev_.data = this;
  ev_timer_init(&timer_, timeoutcb, 0.,
                static_cast<double>(config.timeout) / NGTCP2_SECONDS);
  timer_.data = this;
  ev_timer_init(&rttimer_, retransmitcb, 0., 0.);
  rttimer_.data = this;
}

Handler::~Handler() {
  if (!config.quiet) {
    std::cerr << scid_ << " Closing QUIC connection " << std::endl;
  }

  ev_timer_stop(loop_, &rttimer_);
  ev_timer_stop(loop_, &timer_);
  ev_io_stop(loop_, &wev_);

  if (qlog_) {
    fclose(qlog_);
  }
}

namespace {
int handshake_completed(ngtcp2_conn *conn, void *user_data) {
  auto h = static_cast<Handler *>(user_data);

  if (!config.quiet) {
    debug::handshake_completed(conn, user_data);
  }

  if (h->handshake_completed() != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }

  return 0;
}
} // namespace

int Handler::handshake_completed() {
  if (!config.quiet) {
    std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name()
              << std::endl;
    std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn()
              << std::endl;
  }

  std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token;

  auto path = ngtcp2_conn_get_path(conn_);
  auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
               std::chrono::system_clock::now().time_since_epoch())
               .count();

  auto tokenlen = ngtcp2_crypto_generate_regular_token(
      token.data(), config.static_secret.data(), config.static_secret.size(),
      path->remote.addr, path->remote.addrlen, t);
  if (tokenlen < 0) {
    if (!config.quiet) {
      std::cerr << "Unable to generate token" << std::endl;
    }
    return 0;
  }

  if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen);
      rv != 0) {
    if (!config.quiet) {
      std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv)
                << std::endl;
    }
    return -1;
  }

  return 0;
}

namespace {
int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
               const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
  if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }

  if (!config.quiet && config.show_secret) {
    debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN);
  }

  return 0;
}
} // namespace

namespace {
int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
                     uint64_t offset, const uint8_t *data, size_t datalen,
                     void *user_data) {
  if (!config.quiet && !config.no_quic_dump) {
    debug::print_crypto_data(crypto_level, data, datalen);
  }

  return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data,
                                           datalen, user_data);
}
} // namespace

namespace {
int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
                     uint64_t offset, const uint8_t *data, size_t datalen,
                     void *user_data, void *stream_user_data) {
  auto h = static_cast<Handler *>(user_data);

  if (h->recv_stream_data(flags, stream_id, data, datalen) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }

  return 0;
}
} // namespace

namespace {
int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
                             uint64_t offset, uint64_t datalen, void *user_data,
                             void *stream_user_data) {
  auto h = static_cast<Handler *>(user_data);
  if (h->acked_stream_data_offset(stream_id, offset, datalen) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }
  return 0;
}
} // namespace

int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t offset,
                                      uint64_t datalen) {
  auto it = streams_.find(stream_id);
  assert(it != std::end(streams_));
  auto &stream = (*it).second;
  (void)stream;

  assert(static_cast<uint64_t>(stream->respbuf.end - stream->respbuf.begin) >=
         offset + datalen);

  return 0;
}

namespace {
int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) {
  auto h = static_cast<Handler *>(user_data);
  h->on_stream_open(stream_id);
  return 0;
}
} // namespace

void Handler::on_stream_open(int64_t stream_id) {
  if (!ngtcp2_is_bidi_stream(stream_id)) {
    return;
  }
  auto it = streams_.find(stream_id);
  (void)it;
  assert(it == std::end(streams_));
  streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this));
}

namespace {
int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
                 uint64_t app_error_code, void *user_data,
                 void *stream_user_data) {
  auto h = static_cast<Handler *>(user_data);
  if (h->on_stream_close(stream_id, app_error_code) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }
  return 0;
}
} // namespace

namespace {
void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
  auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
  std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
}
} // namespace

namespace {
int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
                          size_t cidlen, void *user_data) {
  if (util::generate_secure_random(cid->data, cidlen) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }

  cid->datalen = cidlen;
  if (ngtcp2_crypto_generate_stateless_reset_token(
          token, config.static_secret.data(), config.static_secret.size(),
          cid) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }

  auto h = static_cast<Handler *>(user_data);
  h->server()->associate_cid(cid, h);

  return 0;
}
} // namespace

namespace {
int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
                         void *user_data) {
  auto h = static_cast<Handler *>(user_data);
  h->server()->dissociate_cid(cid);
  return 0;
}
} // namespace

namespace {
int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
               ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
               ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
               const uint8_t *current_rx_secret,
               const uint8_t *current_tx_secret, size_t secretlen,
               void *user_data) {
  auto h = static_cast<Handler *>(user_data);
  if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
                    tx_iv, current_rx_secret, current_tx_secret,
                    secretlen) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }
  return 0;
}
} // namespace

namespace {
int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
                    ngtcp2_path_validation_result res, void *user_data) {
  if (!config.quiet) {
    debug::path_validation(path, res);
  }
  return 0;
}
} // namespace

namespace {
int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
                           uint64_t max_data, void *user_data,
                           void *stream_user_data) {
  auto h = static_cast<Handler *>(user_data);
  if (h->extend_max_stream_data(stream_id, max_data) != 0) {
    return NGTCP2_ERR_CALLBACK_FAILURE;
  }
  return 0;
}
} // namespace

int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
  auto it = streams_.find(stream_id);
  assert(it != std::end(streams_));
  auto &stream = (*it).second;

  if (nghttp3_buf_len(&stream->respbuf)) {
    sendq_.emplace(stream.get());
  }

  return 0;
}

namespace {
void write_qlog(void *user_data, uint32_t flags, const void *data,
                size_t datalen) {
  auto h = static_cast<Handler *>(user_data);
  h->write_qlog(data, datalen);
}
} // namespace

void Handler::write_qlog(const void *data, size_t datalen) {
  assert(qlog_);
  fwrite(data, 1, datalen, qlog_);
}

int Handler::init(const Endpoint &ep, const Address &local_addr,
                  const sockaddr *sa, socklen_t salen, const ngtcp2_cid *dcid,
                  const ngtcp2_cid *scid, const ngtcp2_cid *ocid,
                  const uint8_t *token, size_t tokenlen, uint32_t version,
                  const TLSServerContext &tls_ctx) {
  auto callbacks = ngtcp2_callbacks{
      nullptr, // client_initial
      ngtcp2_crypto_recv_client_initial_cb,
      ::recv_crypto_data,
      ::handshake_completed,
      nullptr, // recv_version_negotiation
      ngtcp2_crypto_encrypt_cb,
      ngtcp2_crypto_decrypt_cb,
      do_hp_mask,
      ::recv_stream_data,
      ::acked_stream_data_offset,
      stream_open,
      stream_close,
      nullptr, // recv_stateless_reset
      nullptr, // recv_retry
      nullptr, // extend_max_streams_bidi
      nullptr, // extend_max_streams_uni
      rand,
      get_new_connection_id,
      remove_connection_id,
      ::update_key,
      path_validation,
      nullptr, // select_preferred_addr
      nullptr, // stream_reset
      nullptr, // extend_max_remote_streams_bidi
      nullptr, // extend_max_remote_streams_uni
      ::extend_max_stream_data,
      nullptr, // dcid_status
      nullptr, // handshake_confirmed
      nullptr, // recv_new_token
      ngtcp2_crypto_delete_crypto_aead_ctx_cb,
      ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
      nullptr, // recv_datagram
      nullptr, // ack_datagram
      nullptr, // lost_datagram
      ngtcp2_crypto_get_path_challenge_data_cb,
  };

  scid_.datalen = NGTCP2_SV_SCIDLEN;
  if (util::generate_secure_random(scid_.data, scid_.datalen) != 0) {
    std::cerr << "Could not generate connection ID" << std::endl;
    return -1;
  }

  ngtcp2_settings settings;
  ngtcp2_settings_default(&settings);
  settings.log_printf = config.quiet ? nullptr : debug::log_printf;
  settings.initial_ts = util::timestamp(loop_);
  settings.token = ngtcp2_vec{const_cast<uint8_t *>(token), tokenlen};
  settings.cc_algo = config.cc_algo;
  settings.initial_rtt = config.initial_rtt;
  settings.handshake_timeout = config.handshake_timeout;
  if (config.max_udp_payload_size) {
    settings.max_udp_payload_size = config.max_udp_payload_size;
    settings.no_udp_payload_size_shaping = 1;
  } else {
    settings.max_udp_payload_size = server_max_udp_payload_size;
    settings.assume_symmetric_path = 1;
  }
  if (!config.qlog_dir.empty()) {
    auto path = std::string{config.qlog_dir};
    path += '/';
    path += util::format_hex(scid_.data, scid_.datalen);
    path += ".sqlog";
    qlog_ = fopen(path.c_str(), "w");
    if (qlog_ == nullptr) {
      std::cerr << "Could not open qlog file " << std::quoted(path) << ": "
                << strerror(errno) << std::endl;
      return -1;
    }
    settings.qlog.write = ::write_qlog;
    settings.qlog.odcid = *scid;
  }

  ngtcp2_transport_params params;
  ngtcp2_transport_params_default(&params);
  params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
  params.initial_max_stream_data_bidi_remote =
      config.max_stream_data_bidi_remote;
  params.initial_max_stream_data_uni = config.max_stream_data_uni;
  params.initial_max_data = config.max_data;
  params.initial_max_streams_bidi = config.max_streams_bidi;
  params.initial_max_streams_uni = 0;
  params.max_idle_timeout = config.timeout;
  params.stateless_reset_token_present = 1;
  params.active_connection_id_limit = 7;

  if (ocid) {
    params.original_dcid = *ocid;
    params.retry_scid = *scid;
    params.retry_scid_present = 1;
  } else {
    params.original_dcid = *scid;
  }

  if (util::generate_secure_random(params.stateless_reset_token,
                                   sizeof(params.stateless_reset_token)) != 0) {
    std::cerr << "Could not generate stateless reset token" << std::endl;
    return -1;
  }

  if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) {
    params.preferred_address_present = 1;
    if (config.preferred_ipv4_addr.len) {
      auto &dest = params.preferred_address.ipv4_addr;
      const auto &addr = config.preferred_ipv4_addr;
      assert(sizeof(dest) == sizeof(addr.su.in.sin_addr));
      memcpy(&dest, &addr.su.in.sin_addr, sizeof(dest));
      params.preferred_address.ipv4_port = htons(addr.su.in.sin_port);
      params.preferred_address.ipv4_present = 1;
    }
    if (config.preferred_ipv6_addr.len) {
      auto &dest = params.preferred_address.ipv6_addr;
      const auto &addr = config.preferred_ipv6_addr;
      assert(sizeof(dest) == sizeof(addr.su.in6.sin6_addr));
      memcpy(&dest, &addr.su.in6.sin6_addr, sizeof(dest));
      params.preferred_address.ipv6_port = htons(addr.su.in6.sin6_port);
      params.preferred_address.ipv6_present = 1;
    }

    auto &token = params.preferred_address.stateless_reset_token;
    if (util::generate_secure_random(token, sizeof(token)) != 0) {
      std::cerr << "Could not generate preferred address stateless reset token"
                << std::endl;
      return -1;
    }

    params.preferred_address.cid.datalen = NGTCP2_SV_SCIDLEN;
    if (util::generate_secure_random(params.preferred_address.cid.data,
                                     params.preferred_address.cid.datalen) !=
        0) {
      std::cerr << "Could not generate preferred address connection ID"
                << std::endl;
      return -1;
    }
  }

  auto path = ngtcp2_path{
      {
          const_cast<sockaddr *>(&local_addr.su.sa),
          local_addr.len,
      },
      {
          const_cast<sockaddr *>(sa),
          salen,
      },
      const_cast<Endpoint *>(&ep),
  };
  if (auto rv =
          ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version,
                                 &callbacks, &settings, &params, nullptr, this);
      rv != 0) {
    std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl;
    return -1;
  }

  if (tls_session_.init(tls_ctx, this) != 0) {
    return -1;
  }

  tls_session_.enable_keylog();

  ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle());

  ev_io_set(&wev_, ep.fd, EV_WRITE);
  ev_timer_again(loop_, &timer_);

  return 0;
}

int Handler::feed_data(const Endpoint &ep, const Address &local_addr,
                       const sockaddr *sa, socklen_t salen,
                       const ngtcp2_pkt_info *pi, uint8_t *data,
                       size_t datalen) {
  auto path = ngtcp2_path{
      {
          const_cast<sockaddr *>(&local_addr.su.sa),
          local_addr.len,
      },
      {
          const_cast<sockaddr *>(sa),
          salen,
      },
      const_cast<Endpoint *>(&ep),
  };

  if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen,
                                     util::timestamp(loop_));
      rv != 0) {
    std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
    switch (rv) {
    case NGTCP2_ERR_DRAINING:
      start_draining_period();
      return NETWORK_ERR_CLOSE_WAIT;
    case NGTCP2_ERR_RETRY:
      return NETWORK_ERR_RETRY;
    case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
    case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
    case NGTCP2_ERR_TRANSPORT_PARAM:
      // If rv indicates transport_parameters related error, we should
      // send TRANSPORT_PARAMETER_ERROR even if last_error_.code is
      // already set.  This is because OpenSSL might set Alert.
      last_error_ = quic_err_transport(rv);
      break;
    case NGTCP2_ERR_DROP_CONN:
      return NETWORK_ERR_DROP_CONN;
    default:
      if (!last_error_.code) {
        last_error_ = quic_err_transport(rv);
      }
    }
    return handle_error();
  }

  return 0;
}

int Handler::on_read(const Endpoint &ep, const Address &local_addr,
                     const sockaddr *sa, socklen_t salen,
                     const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) {
  if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data, datalen);
      rv != 0) {
    return rv;
  }

  reset_idle_timer();

  return 0;
}

void Handler::reset_idle_timer() {
  auto now = util::timestamp(loop_);
  auto idle_expiry = ngtcp2_conn_get_idle_expiry(conn_);
  timer_.repeat =
      idle_expiry > now
          ? static_cast<ev_tstamp>(idle_expiry - now) / NGTCP2_SECONDS
          : 1e-9;

  if (!config.quiet) {
    std::cerr << "Set idle timer=" << std::fixed << timer_.repeat << "s"
              << std::defaultfloat << std::endl;
  }

  ev_timer_again(loop_, &timer_);
}

int Handler::handle_expiry() {
  auto now = util::timestamp(loop_);
  if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) {
    std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv)
              << std::endl;
    last_error_ = quic_err_transport(rv);
    return handle_error();
  }

  return 0;
}

int Handler::on_write() {
  if (ngtcp2_conn_is_in_closing_period(conn_) ||
      ngtcp2_conn_is_in_draining_period(conn_)) {
    return 0;
  }

  if (auto rv = write_streams(); rv != 0) {
    return rv;
  }

  schedule_retransmit();

  return 0;
}

int Handler::write_streams() {
  ngtcp2_vec vec;
  ngtcp2_path_storage ps, prev_ps;
  uint32_t prev_ecn = 0;
  size_t pktcnt = 0;
  auto max_udp_payload_size = ngtcp2_conn_get_path_max_udp_payload_size(conn_);
  size_t max_pktcnt =
      std::min(static_cast<size_t>(64_k), ngtcp2_conn_get_send_quantum(conn_)) /
      max_udp_payload_size;
  std::array<uint8_t, 64_k> buf;
  uint8_t *bufpos = buf.data();
  ngtcp2_pkt_info pi;
  auto ts = util::timestamp(loop_);

  ngtcp2_path_storage_zero(&ps);
  ngtcp2_path_storage_zero(&prev_ps);

  if (config.cc_algo != NGTCP2_CC_ALGO_BBR &&
      config.cc_algo != NGTCP2_CC_ALGO_BBR2) {
    /* If bbr is chosen, pacing is enabled.  No need to cap the number
       of datagrams to send. */
    max_pktcnt =
        std::min(max_pktcnt, static_cast<size_t>(config.max_gso_dgrams));
  }

  for (;;) {
    int64_t stream_id = -1;
    size_t vcnt = 0;
    uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
    Stream *stream = nullptr;

    if (!sendq_.empty() && ngtcp2_conn_get_max_data_left(conn_)) {
      stream = *std::begin(sendq_);

      stream_id = stream->stream_id;
      vec.base = stream->respbuf.pos;
      vec.len = nghttp3_buf_len(&stream->respbuf);
      vcnt = 1;
      flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
    }

    ngtcp2_ssize ndatalen;

    auto nwrite = ngtcp2_conn_writev_stream(conn_, &ps.path, &pi, bufpos,
                                            max_udp_payload_size, &ndatalen,
                                            flags, stream_id, &vec, vcnt, ts);
    if (nwrite < 0) {
      switch (nwrite) {
      case NGTCP2_ERR_STREAM_DATA_BLOCKED:
      case NGTCP2_ERR_STREAM_SHUT_WR:
        assert(ndatalen == -1);
        sendq_.erase(std::begin(sendq_));
        continue;
      case NGTCP2_ERR_WRITE_MORE:
        assert(ndatalen >= 0);
        stream->respbuf.pos += ndatalen;
        if (nghttp3_buf_len(&stream->respbuf) == 0) {
          sendq_.erase(std::begin(sendq_));
        }
        continue;
      }

      assert(ndatalen == -1);

      std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite)
                << std::endl;
      last_error_ = quic_err_transport(nwrite);
      return handle_error();
    } else if (ndatalen >= 0) {
      stream->respbuf.pos += ndatalen;
      if (nghttp3_buf_len(&stream->respbuf) == 0) {
        sendq_.erase(std::begin(sendq_));
      }
    }

    if (nwrite == 0) {
      if (bufpos - buf.data()) {
        server_->send_packet(*static_cast<Endpoint *>(prev_ps.path.user_data),
                             prev_ps.path.local, prev_ps.path.remote, prev_ecn,
                             buf.data(), bufpos - buf.data(),
                             max_udp_payload_size);
        reset_idle_timer();
      }
      // We are congestion limited.
      ngtcp2_conn_update_pkt_tx_time(conn_, ts);
      return 0;
    }

    bufpos += nwrite;

#if NGTCP2_ENABLE_UDP_GSO
    if (pktcnt == 0) {
      ngtcp2_path_copy(&prev_ps.path, &ps.path);
      prev_ecn = pi.ecn;
    } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || prev_ecn != pi.ecn) {
      server_->send_packet(*static_cast<Endpoint *>(prev_ps.path.user_data),
                           prev_ps.path.local, prev_ps.path.remote, prev_ecn,
                           buf.data(), bufpos - buf.data() - nwrite,
                           max_udp_payload_size);

      server_->send_packet(*static_cast<Endpoint *>(ps.path.user_data),
                           ps.path.local, ps.path.remote, pi.ecn,
                           bufpos - nwrite, nwrite, max_udp_payload_size);
      ngtcp2_conn_update_pkt_tx_time(conn_, ts);
      reset_idle_timer();
      ev_io_start(loop_, &wev_);
      return 0;
    }

    if (++pktcnt == max_pktcnt ||
        static_cast<size_t>(nwrite) < max_udp_payload_size) {
      server_->send_packet(*static_cast<Endpoint *>(ps.path.user_data),
                           ps.path.local, ps.path.remote, pi.ecn, buf.data(),
                           bufpos - buf.data(), max_udp_payload_size);
      ngtcp2_conn_update_pkt_tx_time(conn_, ts);
      reset_idle_timer();
      ev_io_start(loop_, &wev_);
      return 0;
    }
#else  // !NGTCP2_ENABLE_UDP_GSO
    reset_idle_timer();

    server_->send_packet(*static_cast<Endpoint *>(ps.path.user_data),
                         ps.path.local, ps.path.remote, pi.ecn, buf.data(),
                         bufpos - buf.data(), 0);
    if (++pktcnt == max_pktcnt) {
      ngtcp2_conn_update_pkt_tx_time(conn_, ts);
      ev_io_start(loop_, &wev_);
      return 0;
    }

    bufpos = buf.data();
#endif // !NGTCP2_ENABLE_UDP_GSO
  }
}

void Handler::signal_write() { ev_io_start(loop_, &wev_); }

bool Handler::draining() const { return draining_; }

void Handler::start_draining_period() {
  draining_ = true;

  ev_timer_stop(loop_, &rttimer_);

  timer_.repeat =
      static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
  ev_timer_again(loop_, &timer_);

  if (!config.quiet) {
    std::cerr << "Draining period has started (" << timer_.repeat << " seconds)"
              << std::endl;
  }
}

int Handler::start_closing_period() {
  if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_)) {
    return 0;
  }

  ev_timer_stop(loop_, &rttimer_);

  timer_.repeat =
      static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
  ev_timer_again(loop_, &timer_);

  if (!config.quiet) {
    std::cerr << "Closing period has started (" << timer_.repeat << " seconds)"
              << std::endl;
  }

  conn_closebuf_ = std::make_unique<Buffer>(NGTCP2_MAX_UDP_PAYLOAD_SIZE);

  ngtcp2_path_storage ps;

  ngtcp2_path_storage_zero(&ps);

  ngtcp2_pkt_info pi;
  if (last_error_.type == QUICErrorType::Transport) {
    auto n = ngtcp2_conn_write_connection_close(
        conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
        last_error_.code, nullptr, 0, util::timestamp(loop_));
    if (n < 0) {
      std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n)
                << std::endl;
      return -1;
    }
    conn_closebuf_->push(n);
  } else {
    auto n = ngtcp2_conn_write_application_close(
        conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
        last_error_.code, nullptr, 0, util::timestamp(loop_));
    if (n < 0) {
      std::cerr << "ngtcp2_conn_write_application_close: " << ngtcp2_strerror(n)
                << std::endl;
      return -1;
    }
    conn_closebuf_->push(n);
  }

  return 0;
}

int Handler::handle_error() {
  if (start_closing_period() != 0) {
    return -1;
  }

  if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) {
    return rv;
  }

  return NETWORK_ERR_CLOSE_WAIT;
}

int Handler::send_conn_close() {
  if (!config.quiet) {
    std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl;
  }

  assert(conn_closebuf_ && conn_closebuf_->size());
  assert(conn_);

  auto path = ngtcp2_conn_get_path(conn_);

  return server_->send_packet(
      *static_cast<Endpoint *>(path->user_data), path->local, path->remote,
      /* ecn = */ 0, conn_closebuf_->rpos(), conn_closebuf_->size(), 0);
}

void Handler::schedule_retransmit() {
  auto expiry = ngtcp2_conn_get_expiry(conn_);
  auto now = util::timestamp(loop_);
  auto t = expiry < now ? 1e-9
                        : static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
  if (!config.quiet) {
    std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
              << std::endl;
  }
  rttimer_.repeat = t;
  ev_timer_again(loop_, &rttimer_);
}

namespace {
int on_msg_begin(http_parser *htp) {
  auto s = static_cast<Stream *>(htp->data);
  if (s->eos) {
    return -1;
  }
  return 0;
}
} // namespace

namespace {
int on_url_cb(http_parser *htp, const char *data, size_t datalen) {
  auto s = static_cast<Stream *>(htp->data);
  s->uri.append(data, datalen);
  return 0;
}
} // namespace

namespace {
int on_msg_complete(http_parser *htp) {
  auto s = static_cast<Stream *>(htp->data);
  s->eos = true;
  if (s->start_response() != 0) {
    return -1;
  }
  return 0;
}
} // namespace

auto htp_settings = http_parser_settings{
    on_msg_begin,    // on_message_begin
    on_url_cb,       // on_url
    nullptr,         // on_status
    nullptr,         // on_header_field
    nullptr,         // on_header_value
    nullptr,         // on_headers_complete
    nullptr,         // on_body
    on_msg_complete, // on_message_complete
    nullptr,         // on_chunk_header,
    nullptr,         // on_chunk_complete
};

int Handler::recv_stream_data(uint32_t flags, int64_t stream_id,
                              const uint8_t *data, size_t datalen) {
  if (!config.quiet && !config.no_quic_dump) {
    debug::print_stream_data(stream_id, data, datalen);
  }

  auto it = streams_.find(stream_id);
  assert(it != std::end(streams_));
  auto &stream = (*it).second;

  if (!stream->eos) {
    auto nread =
        http_parser_execute(&stream->htp, &htp_settings,
                            reinterpret_cast<const char *>(data), datalen);
    if (nread != datalen) {
      if (auto rv = ngtcp2_conn_shutdown_stream(conn_, stream_id,
                                                /* app error code */ 1);
          rv != 0) {
        std::cerr << "ngtcp2_conn_shutdown_stream: " << ngtcp2_strerror(rv)
                  << std::endl;
        last_error_ = quic_err_transport(NGTCP2_ERR_INTERNAL);
        return -1;
      }
    }
  }

  ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, datalen);
  ngtcp2_conn_extend_max_offset(conn_, datalen);

  return 0;
}

int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
                        ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
                        ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                        const uint8_t *current_rx_secret,
                        const uint8_t *current_tx_secret, size_t secretlen) {
  auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
  auto aead = &crypto_ctx->aead;
  auto keylen = ngtcp2_crypto_aead_keylen(aead);
  auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);

  ++nkey_update_;

  std::array<uint8_t, 64> rx_key, tx_key;

  if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
                               rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
                               tx_iv, current_rx_secret, current_tx_secret,
                               secretlen) != 0) {
    return -1;
  }

  if (!config.quiet && config.show_secret) {
    std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
    debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
                         ivlen);
    std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
    debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
                         ivlen);
  }

  return 0;
}

Server *Handler::server() const { return server_; }

int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) {
  if (!config.quiet) {
    std::cerr << "QUIC stream " << stream_id << " closed" << std::endl;
  }

  auto it = streams_.find(stream_id);
  assert(it != std::end(streams_));
  auto &stream = (*it).second;

  sendq_.erase(stream.get());

  if (!config.quiet) {
    std::cerr << "HTTP stream " << stream_id << " closed with error code "
              << app_error_code << std::endl;
  }

  streams_.erase(it);

  if (ngtcp2_is_bidi_stream(stream_id)) {
    assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
    ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
  }

  return 0;
}

void Handler::shutdown_read(int64_t stream_id, int app_error_code) {
  ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
}

void Handler::add_sendq(Stream *stream) { sendq_.emplace(stream); }

namespace {
void sreadcb(struct ev_loop *loop, ev_io *w, int revents) {
  auto ep = static_cast<Endpoint *>(w->data);

  ep->server->on_read(*ep);
}
} // namespace

namespace {
void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) {
  ev_break(loop, EVBREAK_ALL);
}
} // namespace

Server::Server(struct ev_loop *loop, const TLSServerContext &tls_ctx)
    : loop_(loop), tls_ctx_(tls_ctx) {
  ev_signal_init(&sigintev_, siginthandler, SIGINT);
}

Server::~Server() {
  disconnect();
  close();
}

void Server::disconnect() {
  config.tx_loss_prob = 0;

  for (auto &ep : endpoints_) {
    ev_io_stop(loop_, &ep.rev);
  }

  ev_signal_stop(loop_, &sigintev_);

  while (!handlers_.empty()) {
    auto it = std::begin(handlers_);
    auto &h = (*it).second;

    h->handle_error();

    remove(h);
  }
}

void Server::close() {
  for (auto &ep : endpoints_) {
    ::close(ep.fd);
  }

  endpoints_.clear();
}

namespace {
int create_sock(Address &local_addr, const char *addr, const char *port,
                int family) {
  addrinfo hints{};
  addrinfo *res, *rp;
  int val = 1;

  hints.ai_family = family;
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_flags = AI_PASSIVE;

  if (strcmp(addr, "*") == 0) {
    addr = nullptr;
  }

  if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) {
    std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
    return -1;
  }

  auto res_d = defer(freeaddrinfo, res);

  int fd = -1;

  for (rp = res; rp; rp = rp->ai_next) {
    fd = util::create_nonblock_socket(rp->ai_family, rp->ai_socktype,
                                      rp->ai_protocol);
    if (fd == -1) {
      continue;
    }

    if (rp->ai_family == AF_INET6) {
      if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
                     static_cast<socklen_t>(sizeof(val))) == -1) {
        close(fd);
        continue;
      }

      if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
                     static_cast<socklen_t>(sizeof(val))) == -1) {
        close(fd);
        continue;
      }
    } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
                          static_cast<socklen_t>(sizeof(val))) == -1) {
      close(fd);
      continue;
    }

    if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
      break;
    }

    close(fd);
  }

  if (!rp) {
    std::cerr << "Could not bind" << std::endl;
    return -1;
  }

  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
                 static_cast<socklen_t>(sizeof(val))) == -1) {
    close(fd);
    return -1;
  }

  fd_set_recv_ecn(fd, rp->ai_family);

  socklen_t len = sizeof(local_addr.su.storage);
  if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
    std::cerr << "getsockname: " << strerror(errno) << std::endl;
    close(fd);
    return -1;
  }
  local_addr.len = len;
  local_addr.ifindex = 0;

  return fd;
}

} // namespace

namespace {
int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr,
                 const char *port, int af) {
  Address dest;
  auto fd = create_sock(dest, addr, port, af);
  if (fd == -1) {
    return -1;
  }

  endpoints.emplace_back();
  auto &ep = endpoints.back();
  ep.addr = dest;
  ep.fd = fd;
  ev_io_init(&ep.rev, sreadcb, 0, EV_READ);

  return 0;
}
} // namespace

namespace {
int add_endpoint(std::vector<Endpoint> &endpoints, const Address &addr) {
  auto fd = util::create_nonblock_socket(addr.su.sa.sa_family, SOCK_DGRAM, 0);
  if (fd == -1) {
    std::cerr << "socket: " << strerror(errno) << std::endl;
    return -1;
  }

  int val = 1;
  if (addr.su.sa.sa_family == AF_INET6) {
    if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
                   static_cast<socklen_t>(sizeof(val))) == -1) {
      std::cerr << "setsockopt: " << strerror(errno) << std::endl;
      close(fd);
      return -1;
    }

    if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
                   static_cast<socklen_t>(sizeof(val))) == -1) {
      std::cerr << "setsockopt: " << strerror(errno) << std::endl;
      close(fd);
      return -1;
    }
  } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
                        static_cast<socklen_t>(sizeof(val))) == -1) {
    std::cerr << "setsockopt: " << strerror(errno) << std::endl;
    close(fd);
    return -1;
  }

  if (bind(fd, &addr.su.sa, addr.len) == -1) {
    std::cerr << "bind: " << strerror(errno) << std::endl;
    close(fd);
    return -1;
  }

  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
                 static_cast<socklen_t>(sizeof(val))) == -1) {
    close(fd);
    return -1;
  }

  fd_set_recv_ecn(fd, addr.su.sa.sa_family);

  endpoints.emplace_back(Endpoint{});
  auto &ep = endpoints.back();
  ep.addr = addr;
  ep.fd = fd;
  ev_io_init(&ep.rev, sreadcb, 0, EV_READ);

  return 0;
}
} // namespace

int Server::init(const char *addr, const char *port) {
  endpoints_.reserve(4);

  auto ready = false;
  if (!util::numeric_host(addr, AF_INET6) &&
      add_endpoint(endpoints_, addr, port, AF_INET) == 0) {
    ready = true;
  }
  if (!util::numeric_host(addr, AF_INET) &&
      add_endpoint(endpoints_, addr, port, AF_INET6) == 0) {
    ready = true;
  }
  if (!ready) {
    return -1;
  }

  if (config.preferred_ipv4_addr.len &&
      add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) {
    return -1;
  }
  if (config.preferred_ipv6_addr.len &&
      add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) {
    return -1;
  }

  for (auto &ep : endpoints_) {
    ep.server = this;
    ep.rev.data = &ep;

    ev_io_set(&ep.rev, ep.fd, EV_READ);

    ev_io_start(loop_, &ep.rev);
  }

  ev_signal_start(loop_, &sigintev_);

  return 0;
}

int Server::on_read(Endpoint &ep) {
  sockaddr_union su;
  std::array<uint8_t, 64_k> buf;
  ngtcp2_pkt_hd hd;
  size_t pktcnt = 0;
  ngtcp2_pkt_info pi;

  iovec msg_iov;
  msg_iov.iov_base = buf.data();
  msg_iov.iov_len = buf.size();

  msghdr msg{};
  msg.msg_name = &su;
  msg.msg_iov = &msg_iov;
  msg.msg_iovlen = 1;

  uint8_t
      msg_ctrl[CMSG_SPACE(sizeof(uint8_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
  msg.msg_control = msg_ctrl;

  for (; pktcnt < 10;) {
    msg.msg_namelen = sizeof(su);
    msg.msg_controllen = sizeof(msg_ctrl);

    auto nread = recvmsg(ep.fd, &msg, 0);
    if (nread == -1) {
      if (!(errno == EAGAIN || errno == ENOTCONN)) {
        std::cerr << "recvmsg: " << strerror(errno) << std::endl;
      }
      return 0;
    }

    ++pktcnt;

    pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family);
    auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family);
    if (!local_addr) {
      std::cerr << "Unable to obtain local address" << std::endl;
      continue;
    }

    set_port(*local_addr, ep.addr);

    if (!config.quiet) {
      std::array<char, IF_NAMESIZE> ifname;
      std::cerr << "Received packet: local="
                << util::straddr(&local_addr->su.sa, local_addr->len)
                << " remote=" << util::straddr(&su.sa, msg.msg_namelen)
                << " if=" << if_indextoname(local_addr->ifindex, ifname.data())
                << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread
                << " bytes" << std::endl;
    }

    if (debug::packet_lost(config.rx_loss_prob)) {
      if (!config.quiet) {
        std::cerr << "** Simulated incoming packet loss **" << std::endl;
      }
      continue;
    }

    if (nread == 0) {
      continue;
    }

    uint32_t version;
    const uint8_t *dcid, *scid;
    size_t dcidlen, scidlen;

    switch (auto rv = ngtcp2_pkt_decode_version_cid(&version, &dcid, &dcidlen,
                                                    &scid, &scidlen, buf.data(),
                                                    nread, NGTCP2_SV_SCIDLEN);
            rv) {
    case 0:
      break;
    case NGTCP2_ERR_VERSION_NEGOTIATION:
      send_version_negotiation(version, scid, scidlen, dcid, dcidlen, ep,
                               *local_addr, &su.sa, msg.msg_namelen);
      continue;
    default:
      std::cerr << "Could not decode version and CID from QUIC packet header: "
                << ngtcp2_strerror(rv) << std::endl;
      continue;
    }

    auto dcid_key = util::make_cid_key(dcid, dcidlen);

    auto handler_it = handlers_.find(dcid_key);
    if (handler_it == std::end(handlers_)) {
      switch (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv) {
      case 0:
        break;
      case NGTCP2_ERR_RETRY:
        send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen);
        continue;
      case NGTCP2_ERR_VERSION_NEGOTIATION:
        if (!config.quiet) {
          std::cerr << "Unsupported version: Send Version Negotiation"
                    << std::endl;
        }
        send_version_negotiation(hd.version, hd.scid.data, hd.scid.datalen,
                                 hd.dcid.data, hd.dcid.datalen, ep, *local_addr,
                                 &su.sa, msg.msg_namelen);
        continue;
      default:
        if (!config.quiet) {
          std::cerr << "Unexpected packet received: length=" << nread
                    << std::endl;
        }
        continue;
      }

      ngtcp2_cid ocid;
      ngtcp2_cid *pocid = nullptr;

      assert(hd.type == NGTCP2_PKT_INITIAL);

      if (config.validate_addr || hd.token.len) {
        std::cerr << "Perform stateless address validation" << std::endl;
        if (hd.token.len == 0) {
          send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen);
          continue;
        }

        if (hd.token.base[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY &&
            hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
          send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
                                          msg.msg_namelen);
          continue;
        }

        switch (hd.token.base[0]) {
        case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY:
          if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) != 0) {
            send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
                                            msg.msg_namelen);
            continue;
          }
          pocid = &ocid;
          break;
        case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR:
          if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) {
            if (config.validate_addr) {
              send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen);
              continue;
            }

            hd.token.base = nullptr;
            hd.token.len = 0;
          }
          break;
        default:
          if (!config.quiet) {
            std::cerr << "Ignore unrecognized token" << std::endl;
          }
          if (config.validate_addr) {
            send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen);
            continue;
          }

          hd.token.base = nullptr;
          hd.token.len = 0;
          break;
        }
      }

      auto h = std::make_unique<Handler>(loop_, this);
      if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid,
                  pocid, hd.token.base, hd.token.len, hd.version,
                  tls_ctx_) != 0) {
        continue;
      }

      switch (h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
                         buf.data(), nread)) {
      case 0:
        break;
      case NETWORK_ERR_RETRY:
        send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen);
        continue;
      default:
        continue;
      }

      switch (h->on_write()) {
      case 0:
        break;
      default:
        continue;
      }

      std::array<ngtcp2_cid, 2> scids;
      auto conn = h->conn();

      auto num_scid = ngtcp2_conn_get_num_scid(conn);

      assert(num_scid <= scids.size());

      ngtcp2_conn_get_scid(conn, scids.data());

      for (size_t i = 0; i < num_scid; ++i) {
        handlers_.emplace(util::make_cid_key(&scids[i]), h.get());
      }

      handlers_.emplace(dcid_key, h.get());

      h.release();

      continue;
    }

    auto h = (*handler_it).second;
    if (ngtcp2_conn_is_in_closing_period(h->conn())) {
      // TODO do exponential backoff.
      switch (h->send_conn_close()) {
      case 0:
        break;
      default:
        remove(h);
      }
      continue;
    }
    if (h->draining()) {
      continue;
    }

    if (auto rv = h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
                             buf.data(), nread);
        rv != 0) {
      if (rv != NETWORK_ERR_CLOSE_WAIT) {
        remove(h);
      }
      continue;
    }

    h->signal_write();
  }

  return 0;
}

namespace {
uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen,
                                   uint32_t version) {
  uint32_t h = 0x811C9DC5u;
  const uint8_t *p = (const uint8_t *)sa;
  const uint8_t *ep = p + salen;
  for (; p != ep; ++p) {
    h ^= *p;
    h *= 0x01000193u;
  }
  version = htonl(version);
  p = (const uint8_t *)&version;
  ep = p + sizeof(version);
  for (; p != ep; ++p) {
    h ^= *p;
    h *= 0x01000193u;
  }
  h &= 0xf0f0f0f0u;
  h |= 0x0a0a0a0au;
  return h;
}
} // namespace

int Server::send_version_negotiation(uint32_t version, const uint8_t *dcid,
                                     size_t dcidlen, const uint8_t *scid,
                                     size_t scidlen, Endpoint &ep,
                                     const Address &local_addr,
                                     const sockaddr *sa, socklen_t salen) {
  Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
  std::array<uint32_t, 16> sv;

  static_assert(sv.size() >= 2 + (NGTCP2_PROTO_VER_DRAFT_MAX -
                                  NGTCP2_PROTO_VER_DRAFT_MIN + 1));

  sv[0] = generate_reserved_version(sa, salen, version);
  sv[1] = NGTCP2_PROTO_VER_V1;

  size_t svlen = 2;
  for (auto v = NGTCP2_PROTO_VER_DRAFT_MIN; v <= NGTCP2_PROTO_VER_DRAFT_MAX;
       ++v) {
    sv[svlen++] = v;
  }

  auto nwrite = ngtcp2_pkt_write_version_negotiation(
      buf.wpos(), buf.left(),
      std::uniform_int_distribution<uint8_t>(
          0, std::numeric_limits<uint8_t>::max())(randgen),
      dcid, dcidlen, scid, scidlen, sv.data(), svlen);
  if (nwrite < 0) {
    std::cerr << "ngtcp2_pkt_write_version_negotiation: "
              << ngtcp2_strerror(nwrite) << std::endl;
    return -1;
  }

  buf.push(nwrite);

  ngtcp2_addr laddr{
      const_cast<sockaddr *>(&local_addr.su.sa),
      local_addr.len,
  };
  ngtcp2_addr raddr{
      const_cast<sockaddr *>(sa),
      salen,
  };

  if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size(), 0) !=
      NETWORK_ERR_OK) {
    return -1;
  }

  return 0;
}

int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
                       const Address &local_addr, const sockaddr *sa,
                       socklen_t salen) {
  std::array<char, NI_MAXHOST> host;
  std::array<char, NI_MAXSERV> port;

  if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
                            port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
      rv != 0) {
    std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Sending Retry packet to [" << host.data()
              << "]:" << port.data() << std::endl;
  }

  ngtcp2_cid scid;

  scid.datalen = NGTCP2_SV_SCIDLEN;
  if (util::generate_secure_random(scid.data, scid.datalen) != 0) {
    return -1;
  }

  std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token;

  auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
               std::chrono::system_clock::now().time_since_epoch())
               .count();

  auto tokenlen = ngtcp2_crypto_generate_retry_token(
      token.data(), config.static_secret.data(), config.static_secret.size(),
      sa, salen, &scid, &chd->dcid, t);
  if (tokenlen < 0) {
    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Generated address validation token:" << std::endl;
    util::hexdump(stderr, token.data(), tokenlen);
  }

  Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};

  auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version,
                                          &chd->scid, &scid, &chd->dcid,
                                          token.data(), tokenlen);
  if (nwrite < 0) {
    std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl;
    return -1;
  }

  buf.push(nwrite);

  ngtcp2_addr laddr{
      const_cast<sockaddr *>(&local_addr.su.sa),
      local_addr.len,
  };
  ngtcp2_addr raddr{
      const_cast<sockaddr *>(sa),
      salen,
  };

  if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size(), 0) !=
      NETWORK_ERR_OK) {
    return -1;
  }

  return 0;
}

int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd,
                                            Endpoint &ep,
                                            const Address &local_addr,
                                            const sockaddr *sa,
                                            socklen_t salen) {
  Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};

  auto nwrite = ngtcp2_crypto_write_connection_close(
      buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid,
      NGTCP2_INVALID_TOKEN, nullptr, 0);
  if (nwrite < 0) {
    std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl;
    return -1;
  }

  buf.push(nwrite);

  ngtcp2_addr laddr{
      const_cast<sockaddr *>(&local_addr.su.sa),
      local_addr.len,
  };
  ngtcp2_addr raddr{
      const_cast<sockaddr *>(sa),
      salen,
  };

  if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size(), 0) !=
      NETWORK_ERR_OK) {
    return -1;
  }

  return 0;
}

int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
                               const sockaddr *sa, socklen_t salen) {
  std::array<char, NI_MAXHOST> host;
  std::array<char, NI_MAXSERV> port;

  if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
                            port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
      rv != 0) {
    std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Verifying Retry token from [" << host.data()
              << "]:" << port.data() << std::endl;
    util::hexdump(stderr, hd->token.base, hd->token.len);
  }

  auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
               std::chrono::system_clock::now().time_since_epoch())
               .count();

  if (ngtcp2_crypto_verify_retry_token(
          ocid, hd->token.base, hd->token.len, config.static_secret.data(),
          config.static_secret.size(), sa, salen, &hd->dcid,
          10 * NGTCP2_SECONDS, t) != 0) {
    std::cerr << "Could not verify Retry token" << std::endl;

    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Token was successfully validated" << std::endl;
  }

  return 0;
}

int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
                         socklen_t salen) {
  std::array<char, NI_MAXHOST> host;
  std::array<char, NI_MAXSERV> port;

  if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
                            port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
      rv != 0) {
    std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Verifying token from [" << host.data() << "]:" << port.data()
              << std::endl;
    util::hexdump(stderr, hd->token.base, hd->token.len);
  }

  auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
               std::chrono::system_clock::now().time_since_epoch())
               .count();

  if (ngtcp2_crypto_verify_regular_token(hd->token.base, hd->token.len,
                                         config.static_secret.data(),
                                         config.static_secret.size(), sa, salen,
                                         3600 * NGTCP2_SECONDS, t) != 0) {
    std::cerr << "Could not verify token" << std::endl;

    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Token was successfully validated" << std::endl;
  }

  return 0;
}

int Server::send_packet(Endpoint &ep, const ngtcp2_addr &local_addr,
                        const ngtcp2_addr &remote_addr, unsigned int ecn,
                        const uint8_t *data, size_t datalen, size_t gso_size) {
  if (debug::packet_lost(config.tx_loss_prob)) {
    if (!config.quiet) {
      std::cerr << "** Simulated outgoing packet loss **" << std::endl;
    }
    return NETWORK_ERR_OK;
  }

  iovec msg_iov;
  msg_iov.iov_base = const_cast<uint8_t *>(data);
  msg_iov.iov_len = datalen;

  msghdr msg{};
  msg.msg_name = const_cast<sockaddr *>(remote_addr.addr);
  msg.msg_namelen = remote_addr.addrlen;
  msg.msg_iov = &msg_iov;
  msg.msg_iovlen = 1;

  uint8_t msg_ctrl[
#if NGTCP2_ENABLE_UDP_GSO
      CMSG_SPACE(sizeof(uint16_t)) +
#endif // NGTCP2_ENABLE_UDP_GSO
      CMSG_SPACE(sizeof(in6_pktinfo))];

  memset(msg_ctrl, 0, sizeof(msg_ctrl));

  msg.msg_control = msg_ctrl;
  msg.msg_controllen = sizeof(msg_ctrl);

  size_t controllen = 0;

  auto cm = CMSG_FIRSTHDR(&msg);

  switch (local_addr.addr->sa_family) {
  case AF_INET: {
    controllen += CMSG_SPACE(sizeof(in_pktinfo));
    cm->cmsg_level = IPPROTO_IP;
    cm->cmsg_type = IP_PKTINFO;
    cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
    auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm));
    memset(pktinfo, 0, sizeof(in_pktinfo));
    auto addrin = reinterpret_cast<sockaddr_in *>(local_addr.addr);
    pktinfo->ipi_spec_dst = addrin->sin_addr;
    break;
  }
  case AF_INET6: {
    controllen += CMSG_SPACE(sizeof(in6_pktinfo));
    cm->cmsg_level = IPPROTO_IPV6;
    cm->cmsg_type = IPV6_PKTINFO;
    cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
    auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm));
    memset(pktinfo, 0, sizeof(in6_pktinfo));
    auto addrin = reinterpret_cast<sockaddr_in6 *>(local_addr.addr);
    pktinfo->ipi6_addr = addrin->sin6_addr;
    break;
  }
  default:
    assert(0);
  }

#if NGTCP2_ENABLE_UDP_GSO
  if (gso_size && datalen > gso_size) {
    controllen += CMSG_SPACE(sizeof(uint16_t));
    cm = CMSG_NXTHDR(&msg, cm);
    cm->cmsg_level = SOL_UDP;
    cm->cmsg_type = UDP_SEGMENT;
    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
    *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
  }
#endif // NGTCP2_ENABLE_UDP_GSO

  msg.msg_controllen = controllen;

  if (ep.ecn != ecn) {
    ep.ecn = ecn;
    fd_set_ecn(ep.fd, ep.addr.su.storage.ss_family, ecn);
  }

  ssize_t nwrite = 0;

  do {
    nwrite = sendmsg(ep.fd, &msg, 0);
  } while (nwrite == -1 && errno == EINTR);

  if (nwrite == -1) {
    std::cerr << "sendmsg: " << strerror(errno) << std::endl;
    // TODO We have packet which is expected to fail to send (e.g.,
    // path validation to old path).
    return NETWORK_ERR_OK;
  }

  if (!config.quiet) {
    std::cerr << "Sent packet: local="
              << util::straddr(local_addr.addr, local_addr.addrlen)
              << " remote="
              << util::straddr(remote_addr.addr, remote_addr.addrlen)
              << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite
              << " bytes" << std::endl;
  }

  return NETWORK_ERR_OK;
}

void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) {
  handlers_.emplace(util::make_cid_key(cid), h);
}

void Server::dissociate_cid(const ngtcp2_cid *cid) {
  handlers_.erase(util::make_cid_key(cid));
}

void Server::remove(const Handler *h) {
  auto conn = h->conn();

  handlers_.erase(
      util::make_cid_key(ngtcp2_conn_get_client_initial_dcid(conn)));

  std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(conn));
  ngtcp2_conn_get_scid(conn, cids.data());

  for (auto &cid : cids) {
    handlers_.erase(util::make_cid_key(&cid));
  }

  delete h;
}

namespace {
int parse_host_port(Address &dest, int af, const char *first,
                    const char *last) {
  if (std::distance(first, last) == 0) {
    return -1;
  }

  const char *host_begin, *host_end, *it;
  if (*first == '[') {
    host_begin = first + 1;
    it = std::find(host_begin, last, ']');
    if (it == last) {
      return -1;
    }
    host_end = it;
    ++it;
    if (it == last || *it != ':') {
      return -1;
    }
  } else {
    host_begin = first;
    it = std::find(host_begin, last, ':');
    if (it == last) {
      return -1;
    }
    host_end = it;
  }

  if (++it == last) {
    return -1;
  }
  auto svc_begin = it;

  std::array<char, NI_MAXHOST> host;
  *std::copy(host_begin, host_end, std::begin(host)) = '\0';

  addrinfo hints{}, *res;
  hints.ai_family = af;
  hints.ai_socktype = SOCK_DGRAM;

  if (auto rv = getaddrinfo(host.data(), svc_begin, &hints, &res); rv != 0) {
    std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc_begin << ": "
              << gai_strerror(rv) << std::endl;
    return -1;
  }

  dest.len = res->ai_addrlen;
  memcpy(&dest.su, res->ai_addr, res->ai_addrlen);

  freeaddrinfo(res);

  return 0;
}
} // namespace

namespace {
void print_usage() {
  std::cerr << "Usage: server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> "
               "<CERTIFICATE_FILE>"
            << std::endl;
}
} // namespace

namespace {
void config_set_default(Config &config) {
  config = Config{};
  config.tx_loss_prob = 0.;
  config.rx_loss_prob = 0.;
  config.ciphers = util::crypto_default_ciphers();
  config.groups = util::crypto_default_groups();
  config.timeout = 30 * NGTCP2_SECONDS;
  {
    auto path = realpath(".", nullptr);
    assert(path);
    config.htdocs = path;
    free(path);
  }
  config.mime_types_file = "/etc/mime.types";
  config.max_data = 1_m;
  config.max_stream_data_bidi_local = 256_k;
  config.max_stream_data_bidi_remote = 256_k;
  config.max_stream_data_uni = 256_k;
  config.max_streams_bidi = 100;
  config.max_streams_uni = 3;
  config.max_dyn_length = 20_m;
  config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
  config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
  config.max_gso_dgrams = 10;
  config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
}
} // namespace

namespace {
void print_help() {
  print_usage();

  config_set_default(config);

  std::cout << R"(
  <ADDR>      Address to listen to.  '*' binds to any address.
  <PORT>      Port
  <PRIVATE_KEY_FILE>
              Path to private key file
  <CERTIFICATE_FILE>
              Path to certificate file
Options:
  -t, --tx-loss=<P>
              The probability of losing outgoing packets.  <P> must be
              [0.0, 1.0],  inclusive.  0.0 means no  packet loss.  1.0
              means 100% packet loss.
  -r, --rx-loss=<P>
              The probability of losing incoming packets.  <P> must be
              [0.0, 1.0],  inclusive.  0.0 means no  packet loss.  1.0
              means 100% packet loss.
  --ciphers=<CIPHERS>
              Specify the cipher suite list to enable.
              Default: )"
            << config.ciphers << R"(
  --groups=<GROUPS>
              Specify the supported groups.
              Default: )"
            << config.groups << R"(
  -d, --htdocs=<PATH>
              Specify document root.  If this option is not specified,
              the document root is the current working directory.
  -q, --quiet Suppress debug output.
  -s, --show-secret
              Print out secrets unless --quiet is used.
  --timeout=<DURATION>
              Specify idle timeout.
              Default: )"
            << util::format_duration(config.timeout) << R"(
  -V, --validate-addr
              Perform address validation.
  --preferred-ipv4-addr=<ADDR>:<PORT>
              Specify preferred IPv4 address and port.
  --preferred-ipv6-addr=<ADDR>:<PORT>
              Specify preferred IPv6 address and port.  A numeric IPv6
              address  must   be  enclosed  by  '['   and  ']'  (e.g.,
              [::1]:8443)
  --mime-types-file=<PATH>
              Path  to file  that contains  MIME media  types and  the
              extensions.
              Default: )"
            << config.mime_types_file << R"(
  --early-response
              Start  sending response  when  it  receives HTTP  header
              fields  without  waiting  for  request  body.   If  HTTP
              response data is written  before receiving request body,
              STOP_SENDING is sent.
  --verify-client
              Request a  client certificate.   At the moment,  we just
              request a certificate and no verification is done.
  --qlog-dir=<PATH>
              Path to  the directory where  qlog file is  stored.  The
              file name  of each qlog  is the Source Connection  ID of
              server.
  --no-quic-dump
              Disables printing QUIC STREAM and CRYPTO frame data out.
  --no-http-dump
              Disables printing HTTP response body out.
  --max-data=<SIZE>
              The initial connection-level flow control window.
              Default: )"
            << util::format_uint_iec(config.max_data) << R"(
  --max-stream-data-bidi-local=<SIZE>
              The  initial  stream-level  flow control  window  for  a
              bidirectional stream that the local endpoint initiates.
              Default: )"
            << util::format_uint_iec(config.max_stream_data_bidi_local) << R"(
  --max-stream-data-bidi-remote=<SIZE>
              The  initial  stream-level  flow control  window  for  a
              bidirectional stream that the remote endpoint initiates.
              Default: )"
            << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"(
  --max-stream-data-uni=<SIZE>
              The  initial  stream-level  flow control  window  for  a
              unidirectional stream.
              Default: )"
            << util::format_uint_iec(config.max_stream_data_uni) << R"(
  --max-streams-bidi=<N>
              The number of the concurrent bidirectional streams.
              Default: )"
            << config.max_streams_bidi << R"(
  --max-streams-uni=<N>
              The number of the concurrent unidirectional streams.
              Default: )"
            << config.max_streams_uni << R"(
  --max-dyn-length=<SIZE>
              The maximum length of a dynamically generated content.
              Default: )"
            << util::format_uint_iec(config.max_dyn_length) << R"(
  --cc=(cubic|reno|bbr|bbr2)
              The name of congestion controller algorithm.
              Default: )"
            << util::strccalgo(config.cc_algo) << R"(
  --initial-rtt=<DURATION>
              Set an initial RTT.
              Default: )"
            << util::format_duration(config.initial_rtt) << R"(
  --max-udp-payload-size=<SIZE>
              Override maximum UDP payload size that server transmits.
  --send-trailers
              Send trailer fields.
  --max-gso-dgrams=<N>
              Maximum  number of  UDP  datagrams that  are  sent in  a
              single GSO sendmsg call.
              Default: )"
            << config.max_gso_dgrams << R"(
  --handshake-timeout=<DURATION>
              Set the QUIC handshake timeout.
              Default: )"
            << util::format_duration(config.handshake_timeout) << R"(
  -h, --help  Display this help and exit.

---

  The <SIZE> argument is an integer and an optional unit (e.g., 10K is
  10 * 1024).  Units are K, M and G (powers of 1024).

  The <DURATION> argument is an integer and an optional unit (e.g., 1s
  is 1 second and 500ms is 500  milliseconds).  Units are h, m, s, ms,
  us, or ns (hours,  minutes, seconds, milliseconds, microseconds, and
  nanoseconds respectively).  If  a unit is omitted, a  second is used
  as unit.)" << std::endl;
}
} // namespace

std::ofstream keylog_file;

int main(int argc, char **argv) {
  config_set_default(config);

  for (;;) {
    static int flag = 0;
    constexpr static option long_opts[] = {
        {"help", no_argument, nullptr, 'h'},
        {"tx-loss", required_argument, nullptr, 't'},
        {"rx-loss", required_argument, nullptr, 'r'},
        {"htdocs", required_argument, nullptr, 'd'},
        {"quiet", no_argument, nullptr, 'q'},
        {"show-secret", no_argument, nullptr, 's'},
        {"validate-addr", no_argument, nullptr, 'V'},
        {"ciphers", required_argument, &flag, 1},
        {"groups", required_argument, &flag, 2},
        {"timeout", required_argument, &flag, 3},
        {"preferred-ipv4-addr", required_argument, &flag, 4},
        {"preferred-ipv6-addr", required_argument, &flag, 5},
        {"mime-types-file", required_argument, &flag, 6},
        {"early-response", no_argument, &flag, 7},
        {"verify-client", no_argument, &flag, 8},
        {"qlog-dir", required_argument, &flag, 9},
        {"no-quic-dump", no_argument, &flag, 10},
        {"no-http-dump", no_argument, &flag, 11},
        {"max-data", required_argument, &flag, 12},
        {"max-stream-data-bidi-local", required_argument, &flag, 13},
        {"max-stream-data-bidi-remote", required_argument, &flag, 14},
        {"max-stream-data-uni", required_argument, &flag, 15},
        {"max-streams-bidi", required_argument, &flag, 16},
        {"max-streams-uni", required_argument, &flag, 17},
        {"max-dyn-length", required_argument, &flag, 18},
        {"cc", required_argument, &flag, 19},
        {"initial-rtt", required_argument, &flag, 20},
        {"max-udp-payload-size", required_argument, &flag, 21},
        {"send-trailers", no_argument, &flag, 22},
        {"max-gso-dgrams", required_argument, &flag, 25},
        {"handshake-timeout", required_argument, &flag, 26},
        {nullptr, 0, nullptr, 0}};

    auto optidx = 0;
    auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx);
    if (c == -1) {
      break;
    }
    switch (c) {
    case 'd': {
      // --htdocs
      auto path = realpath(optarg, nullptr);
      if (path == nullptr) {
        std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl;
        exit(EXIT_FAILURE);
      }
      config.htdocs = path;
      free(path);
      break;
    }
    case 'h':
      // --help
      print_help();
      exit(EXIT_SUCCESS);
    case 'q':
      // --quiet
      config.quiet = true;
      break;
    case 'r':
      // --rx-loss
      config.rx_loss_prob = strtod(optarg, nullptr);
      break;
    case 's':
      // --show-secret
      config.show_secret = true;
      break;
    case 't':
      // --tx-loss
      config.tx_loss_prob = strtod(optarg, nullptr);
      break;
    case 'V':
      // --validate-addr
      config.validate_addr = true;
      break;
    case '?':
      print_usage();
      exit(EXIT_FAILURE);
    case 0:
      switch (flag) {
      case 1:
        // --ciphers
        config.ciphers = optarg;
        break;
      case 2:
        // --groups
        config.groups = optarg;
        break;
      case 3:
        // --timeout
        if (auto t = util::parse_duration(optarg); !t) {
          std::cerr << "timeout: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.timeout = *t;
        }
        break;
      case 4:
        // --preferred-ipv4-addr
        if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg,
                            optarg + strlen(optarg)) != 0) {
          std::cerr << "preferred-ipv4-addr: could not use "
                    << std::quoted(optarg) << std::endl;
          exit(EXIT_FAILURE);
        }
        break;
      case 5:
        // --preferred-ipv6-addr
        if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg,
                            optarg + strlen(optarg)) != 0) {
          std::cerr << "preferred-ipv6-addr: could not use "
                    << std::quoted(optarg) << std::endl;
          exit(EXIT_FAILURE);
        }
        break;
      case 6:
        // --mime-types-file
        config.mime_types_file = optarg;
        break;
      case 7:
        // --early-response
        config.early_response = true;
        break;
      case 8:
        // --verify-client
        config.verify_client = true;
        break;
      case 9:
        // --qlog-dir
        config.qlog_dir = optarg;
        break;
      case 10:
        // --no-quic-dump
        config.no_quic_dump = true;
        break;
      case 11:
        // --no-http-dump
        config.no_http_dump = true;
        break;
      case 12:
        // --max-data
        if (auto n = util::parse_uint_iec(optarg); !n) {
          std::cerr << "max-data: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_data = *n;
        }
        break;
      case 13:
        // --max-stream-data-bidi-local
        if (auto n = util::parse_uint_iec(optarg); !n) {
          std::cerr << "max-stream-data-bidi-local: invalid argument"
                    << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_stream_data_bidi_local = *n;
        }
        break;
      case 14:
        // --max-stream-data-bidi-remote
        if (auto n = util::parse_uint_iec(optarg); !n) {
          std::cerr << "max-stream-data-bidi-remote: invalid argument"
                    << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_stream_data_bidi_remote = *n;
        }
        break;
      case 15:
        // --max-stream-data-uni
        if (auto n = util::parse_uint_iec(optarg); !n) {
          std::cerr << "max-stream-data-uni: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_stream_data_uni = *n;
        }
        break;
      case 16:
        // --max-streams-bidi
        if (auto n = util::parse_uint(optarg); !n) {
          std::cerr << "max-streams-bidi: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_streams_bidi = *n;
        }
        break;
      case 17:
        // --max-streams-uni
        if (auto n = util::parse_uint(optarg); !n) {
          std::cerr << "max-streams-uni: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_streams_uni = *n;
        }
        break;
      case 18:
        // --max-dyn-length
        if (auto n = util::parse_uint_iec(optarg); !n) {
          std::cerr << "max-dyn-length: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_dyn_length = *n;
        }
        break;
      case 19:
        // --cc
        if (strcmp("cubic", optarg) == 0) {
          config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
          break;
        }
        if (strcmp("reno", optarg) == 0) {
          config.cc_algo = NGTCP2_CC_ALGO_RENO;
          break;
        }
        if (strcmp("bbr", optarg) == 0) {
          config.cc_algo = NGTCP2_CC_ALGO_BBR;
          break;
        }
        if (strcmp("bbr2", optarg) == 0) {
          config.cc_algo = NGTCP2_CC_ALGO_BBR2;
          break;
        }
        std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl;
        exit(EXIT_FAILURE);
      case 20:
        // --initial-rtt
        if (auto t = util::parse_duration(optarg); !t) {
          std::cerr << "initial-rtt: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.initial_rtt = *t;
        }
        break;
      case 21:
        // --max-udp-payload-size
        if (auto n = util::parse_uint_iec(optarg); !n) {
          std::cerr << "max-udp-payload-size: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else if (*n > 64_k) {
          std::cerr << "max-udp-payload-size: must not exceed 65536"
                    << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_udp_payload_size = *n;
        }
        break;
      case 22:
        // --send-trailers
        config.send_trailers = true;
        break;
      case 25:
        // --max-gso-dgrams
        if (auto n = util::parse_uint(optarg); !n) {
          std::cerr << "max-gso-dgrams: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.max_gso_dgrams = *n;
        }
        break;
      case 26:
        // --handshake-timeout
        if (auto t = util::parse_duration(optarg); !t) {
          std::cerr << "handshake-timeout: invalid argument" << std::endl;
          exit(EXIT_FAILURE);
        } else {
          config.handshake_timeout = *t;
        }
        break;
      }
      break;
    default:
      break;
    };
  }

  if (argc - optind < 4) {
    std::cerr << "Too few arguments" << std::endl;
    print_usage();
    exit(EXIT_FAILURE);
  }

  auto addr = argv[optind++];
  auto port = argv[optind++];
  auto private_key_file = argv[optind++];
  auto cert_file = argv[optind++];

  if (auto n = util::parse_uint(port); !n) {
    std::cerr << "port: invalid port number" << std::endl;
    exit(EXIT_FAILURE);
  } else if (*n > 65535) {
    std::cerr << "port: must not exceed 65535" << std::endl;
    exit(EXIT_FAILURE);
  } else {
    config.port = *n;
  }

  if (auto mt = util::read_mime_types(config.mime_types_file); !mt) {
    std::cerr << "mime-types-file: Could not read MIME media types file "
              << std::quoted(config.mime_types_file) << std::endl;
  } else {
    config.mime_types = std::move(*mt);
  }

  TLSServerContext tls_ctx;

  if (tls_ctx.init(private_key_file, cert_file, AppProtocol::HQ) != 0) {
    exit(EXIT_FAILURE);
  }

  if (config.htdocs.back() != '/') {
    config.htdocs += '/';
  }

  std::cerr << "Using document root " << config.htdocs << std::endl;

  auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT);

  auto keylog_filename = getenv("SSLKEYLOGFILE");
  if (keylog_filename) {
    keylog_file.open(keylog_filename, std::ios_base::app);
    if (keylog_file) {
      tls_ctx.enable_keylog();
    }
  }

  if (util::generate_secret(config.static_secret.data(),
                            config.static_secret.size()) != 0) {
    std::cerr << "Unable to generate static secret" << std::endl;
    exit(EXIT_FAILURE);
  }

  Server s(EV_DEFAULT, tls_ctx);
  if (s.init(addr, port) != 0) {
    exit(EXIT_FAILURE);
  }

  ev_run(EV_DEFAULT, 0);

  s.disconnect();
  s.close();

  return EXIT_SUCCESS;
}
