text: /*
   * 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 "server.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 {
  constexpr size_t MAX_DYNBUFLEN = 10 * 1024 * 1024;
  } // namespace
  
  namespace {
  auto randgen = util::make_mt19937();
  } // namespace
  
  Config config{};
  
  Stream::Stream(int64_t stream_id, Handler *handler)
      : stream_id(stream_id),
        handler(handler),
        data(nullptr),
        datalen(0),
        dynresp(false),
        dyndataleft(0),
        dynbuflen(0) {}
  
  namespace {
  constexpr char NGTCP2_SERVER[] = "nghttp3/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, bool is_connect) {
    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, &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) {
    data = static_cast<uint8_t *>(fe.map);
    datalen = fe.len;
  }
  
  int64_t Stream::find_dyn_length(const std::string_view &path) {
    assert(path[0] == '/');
  
    if (path.size() == 1) {
      return -1;
    }
  
    uint64_t n = 0;
  
    for (auto it = std::begin(path) + 1; it != std::end(path); ++it) {
      if (*it < '0' || '9' < *it) {
        return -1;
      }
      auto d = *it - '0';
      if (n > (((1ull << 62) - 1) - d) / 10) {
        return -1;
      }
      n = n * 10 + d;
      if (n > config.max_dyn_length) {
        return -1;
      }
    }
  
    return static_cast<int64_t>(n);
  }
  
  namespace {
  nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
                          size_t veccnt, uint32_t *pflags, void *user_data,
                          void *stream_user_data) {
    auto stream = static_cast<Stream *>(stream_user_data);
  
    vec[0].base = stream->data;
    vec[0].len = stream->datalen;
    *pflags |= NGHTTP3_DATA_FLAG_EOF;
    if (config.send_trailers) {
      *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
    }
  
    return 1;
  }
  } // namespace
  
  auto dyn_buf = std::make_unique<std::array<uint8_t, 16_k>>();
  
  namespace {
  nghttp3_ssize dyn_read_data(nghttp3_conn *conn, int64_t stream_id,
                              nghttp3_vec *vec, size_t veccnt, uint32_t *pflags,
                              void *user_data, void *stream_user_data) {
    auto stream = static_cast<Stream *>(stream_user_data);
  
    if (stream->dynbuflen > MAX_DYNBUFLEN) {
      return NGHTTP3_ERR_WOULDBLOCK;
    }
  
    auto len =
        std::min(dyn_buf->size(), static_cast<size_t>(stream->dyndataleft));
  
    vec[0].base = dyn_buf->data();
    vec[0].len = len;
  
    stream->dynbuflen += len;
    stream->dyndataleft -= len;
  
    if (stream->dyndataleft == 0) {
      *pflags |= NGHTTP3_DATA_FLAG_EOF;
      if (config.send_trailers) {
        *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
        auto stream_id_str = std::to_string(stream_id);
        std::array<nghttp3_nv, 1> trailers{
            util::make_nv("x-ngtcp2-stream-id", stream_id_str),
        };
  
        if (auto rv = nghttp3_conn_submit_trailers(
                conn, stream_id, trailers.data(), trailers.size());
            rv != 0) {
          std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
                    << std::endl;
          return NGHTTP3_ERR_CALLBACK_FAILURE;
        }
      }
    }
  
    return 1;
  }
  } // namespace
  
  void Stream::http_acked_stream_data(uint64_t datalen) {
    if (!dynresp) {
      return;
    }
  
    assert(dynbuflen >= datalen);
  
    dynbuflen -= datalen;
  }
  
  int Stream::send_status_response(nghttp3_conn *httpconn,
                                   unsigned int status_code,
                                   const std::vector<HTTPHeader> &extra_headers) {
    status_resp_body = make_status_body(status_code);
  
    auto status_code_str = std::to_string(status_code);
    auto content_length_str = std::to_string(status_resp_body.size());
  
    std::vector<nghttp3_nv> nva(4 + extra_headers.size());
    nva[0] = util::make_nv(":status", status_code_str);
    nva[1] = util::make_nv("server", NGTCP2_SERVER);
    nva[2] = util::make_nv("content-type", "text/html; charset=utf-8");
    nva[3] = util::make_nv("content-length", content_length_str);
    for (size_t i = 0; i < extra_headers.size(); ++i) {
      auto &hdr = extra_headers[i];
      auto &nv = nva[4 + i];
      nv = util::make_nv(hdr.name, hdr.value);
    }
  
    data = (uint8_t *)status_resp_body.data();
    datalen = status_resp_body.size();
  
    nghttp3_data_reader dr{};
    dr.read_data = read_data;
  
    if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(),
                                               nva.size(), &dr);
        rv != 0) {
      std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (config.send_trailers) {
      auto stream_id_str = std::to_string(stream_id);
      std::array<nghttp3_nv, 1> trailers{
          util::make_nv("x-ngtcp2-stream-id", stream_id_str),
      };
  
      if (auto rv = nghttp3_conn_submit_trailers(
              httpconn, stream_id, trailers.data(), trailers.size());
          rv != 0) {
        std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
                  << std::endl;
        return -1;
      }
    }
  
    handler->shutdown_read(stream_id, NGHTTP3_H3_NO_ERROR);
  
    return 0;
  }
  
  int Stream::send_redirect_response(nghttp3_conn *httpconn,
                                     unsigned int status_code,
                                     const std::string_view &path) {
    return send_status_response(httpconn, status_code, {{"location", path}});
  }
  
  int Stream::start_response(nghttp3_conn *httpconn) {
    // TODO This should be handled by nghttp3
    if (uri.empty() || method.empty()) {
      return send_status_response(httpconn, 400);
    }
  
    auto req = request_path(uri, method == "CONNECT");
    if (req.path.empty()) {
      return send_status_response(httpconn, 400);
    }
  
    auto dyn_len = find_dyn_length(req.path);
  
    int64_t content_length = -1;
    nghttp3_data_reader dr{};
    std::string content_type = "text/plain";
  
    if (dyn_len == -1) {
      auto path = config.htdocs + req.path;
      auto [fe, rv] = open_file(path);
      if (rv != 0) {
        send_status_response(httpconn, 404);
        return 0;
      }
  
      if (fe.flags & FILE_ENTRY_TYPE_DIR) {
        send_redirect_response(httpconn, 308,
                               path.substr(config.htdocs.size() - 1) + '/');
        return 0;
      }
  
      content_length = fe.len;
  
      if (method != "HEAD") {
        map_file(fe);
      }
  
      dr.read_data = read_data;
  
      auto ext = std::end(req.path) - 1;
      for (; ext != std::begin(req.path) && *ext != '.' && *ext != '/'; --ext)
        ;
      if (*ext == '.') {
        ++ext;
        auto it = config.mime_types.find(std::string{ext, std::end(req.path)});
        if (it != std::end(config.mime_types)) {
          content_type = (*it).second;
        }
      }
    } else {
      content_length = dyn_len;
      dynresp = true;
      dr.read_data = dyn_read_data;
  
      if (method != "HEAD") {
        datalen = dyn_len;
        dyndataleft = dyn_len;
      }
  
      content_type = "application/octet-stream";
    }
  
    auto content_length_str = std::to_string(content_length);
  
    std::array<nghttp3_nv, 5> nva{
        util::make_nv(":status", "200"),
        util::make_nv("server", NGTCP2_SERVER),
        util::make_nv("content-type", content_type),
        util::make_nv("content-length", content_length_str),
    };
  
    size_t nvlen = 4;
  
    std::string prival;
  
    if (req.pri.urgency != -1 || req.pri.inc != -1) {
      nghttp3_pri pri;
  
      if (auto rv = nghttp3_conn_get_stream_priority(httpconn, &pri, stream_id);
          rv != 0) {
        std::cerr << "nghttp3_conn_get_stream_priority: " << nghttp3_strerror(rv)
                  << std::endl;
        return -1;
      }
  
      if (req.pri.urgency != -1) {
        pri.urgency = req.pri.urgency;
      }
      if (req.pri.inc != -1) {
        pri.inc = req.pri.inc;
      }
  
      if (auto rv = nghttp3_conn_set_stream_priority(httpconn, stream_id, &pri);
          rv != 0) {
        std::cerr << "nghttp3_conn_set_stream_priority: " << nghttp3_strerror(rv)
                  << std::endl;
        return -1;
      }
  
      prival = "u=";
      prival += pri.urgency + '0';
      prival += ",i";
      if (!pri.inc) {
        prival += "=?0";
      }
  
      nva[nvlen++] = util::make_nv("priority", prival);
    }
  
    if (!config.quiet) {
      debug::print_http_response_headers(stream_id, nva.data(), nvlen);
    }
  
    if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(),
                                               nvlen, &dr);
        rv != 0) {
      std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (config.send_trailers && dyn_len == -1) {
      auto stream_id_str = std::to_string(stream_id);
      std::array<nghttp3_nv, 1> trailers{
          util::make_nv("x-ngtcp2-stream-id", stream_id_str),
      };
  
      if (auto rv = nghttp3_conn_submit_trailers(
              httpconn, stream_id, trailers.data(), trailers.size());
          rv != 0) {
        std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
                  << std::endl;
        return -1;
      }
    }
  
    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_{},
        httpconn_{nullptr},
        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;
  
    application_tx_key_cb_ = [this]() { return setup_httpconn(); };
  }
  
  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 (httpconn_) {
      nghttp3_conn_del(httpconn_);
    }
  
    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;
    }
  
    if (tls_session_.send_session_ticket() != 0) {
      std::cerr << "Unable to send session ticket" << 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, datalen) != 0) {
      return NGTCP2_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) {
    if (!httpconn_) {
      return 0;
    }
  
    if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen);
        rv != 0) {
      std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    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 (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
      app_error_code = NGHTTP3_H3_NO_ERROR;
    }
  
    if (h->on_stream_close(stream_id, app_error_code) != 0) {
      return NGTCP2_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  namespace {
  int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
                   uint64_t app_error_code, void *user_data,
                   void *stream_user_data) {
    auto h = static_cast<Handler *>(user_data);
    if (h->on_stream_reset(stream_id) != 0) {
      return NGTCP2_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::on_stream_reset(int64_t stream_id) {
    if (httpconn_) {
      if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
          rv != 0) {
        std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv)
                  << std::endl;
        return -1;
      }
    }
    return 0;
  }
  
  namespace {
  int stream_stop_sending(ngtcp2_conn *conn, 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_stop_sending(stream_id) != 0) {
      return NGTCP2_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::on_stream_stop_sending(int64_t stream_id) {
    if (!httpconn_) {
      return 0;
    }
  
    if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
        rv != 0) {
      std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    return 0;
  }
  
  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_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
                                     void *user_data) {
    auto h = static_cast<Handler *>(user_data);
    h->extend_max_remote_streams_bidi(max_streams);
    return 0;
  }
  } // namespace
  
  void Handler::extend_max_remote_streams_bidi(uint64_t max_streams) {
    if (!httpconn_) {
      return;
    }
  
    nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams);
  }
  
  namespace {
  int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
                     size_t datalen, void *user_data, void *stream_user_data) {
    if (!config.quiet && !config.no_http_dump) {
      debug::print_http_data(stream_id, data, datalen);
    }
    auto h = static_cast<Handler *>(user_data);
    h->http_consume(stream_id, datalen);
    return 0;
  }
  } // namespace
  
  namespace {
  int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
                            size_t nconsumed, void *user_data,
                            void *stream_user_data) {
    auto h = static_cast<Handler *>(user_data);
    h->http_consume(stream_id, nconsumed);
    return 0;
  }
  } // namespace
  
  void Handler::http_consume(int64_t stream_id, size_t nconsumed) {
    ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
    ngtcp2_conn_extend_max_offset(conn_, nconsumed);
  }
  
  namespace {
  int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id,
                                 void *user_data, void *stream_user_data) {
    if (!config.quiet) {
      debug::print_http_begin_request_headers(stream_id);
    }
  
    auto h = static_cast<Handler *>(user_data);
    h->http_begin_request_headers(stream_id);
    return 0;
  }
  } // namespace
  
  void Handler::http_begin_request_headers(int64_t stream_id) {
    auto it = streams_.find(stream_id);
    assert(it != std::end(streams_));
    auto &stream = (*it).second;
  
    nghttp3_conn_set_stream_user_data(httpconn_, stream_id, stream.get());
  }
  
  namespace {
  int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
                               int32_t token, nghttp3_rcbuf *name,
                               nghttp3_rcbuf *value, uint8_t flags,
                               void *user_data, void *stream_user_data) {
    if (!config.quiet) {
      debug::print_http_header(stream_id, name, value, flags);
    }
  
    auto h = static_cast<Handler *>(user_data);
    auto stream = static_cast<Stream *>(stream_user_data);
    h->http_recv_request_header(stream, token, name, value);
    return 0;
  }
  } // namespace
  
  void Handler::http_recv_request_header(Stream *stream, int32_t token,
                                         nghttp3_rcbuf *name,
                                         nghttp3_rcbuf *value) {
    auto v = nghttp3_rcbuf_get_buf(value);
  
    switch (token) {
    case NGHTTP3_QPACK_TOKEN__PATH:
      stream->uri = std::string{v.base, v.base + v.len};
      break;
    case NGHTTP3_QPACK_TOKEN__METHOD:
      stream->method = std::string{v.base, v.base + v.len};
      break;
    case NGHTTP3_QPACK_TOKEN__AUTHORITY:
      stream->authority = std::string{v.base, v.base + v.len};
      break;
    }
  }
  
  namespace {
  int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id,
                               void *user_data, void *stream_user_data) {
    if (!config.quiet) {
      debug::print_http_end_headers(stream_id);
    }
  
    auto h = static_cast<Handler *>(user_data);
    auto stream = static_cast<Stream *>(stream_user_data);
    if (h->http_end_request_headers(stream) != 0) {
      return NGHTTP3_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::http_end_request_headers(Stream *stream) {
    if (config.early_response) {
      if (start_response(stream) != 0) {
        return -1;
      }
  
      shutdown_read(stream->stream_id, NGHTTP3_H3_NO_ERROR);
    }
    return 0;
  }
  
  namespace {
  int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
                      void *stream_user_data) {
    auto h = static_cast<Handler *>(user_data);
    auto stream = static_cast<Stream *>(stream_user_data);
    if (h->http_end_stream(stream) != 0) {
      return NGHTTP3_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::http_end_stream(Stream *stream) {
    if (!config.early_response) {
      return start_response(stream);
    }
    return 0;
  }
  
  int Handler::start_response(Stream *stream) {
    return stream->start_response(httpconn_);
  }
  
  namespace {
  int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
                             uint64_t datalen, void *user_data,
                             void *stream_user_data) {
    auto h = static_cast<Handler *>(user_data);
    auto stream = static_cast<Stream *>(stream_user_data);
    h->http_acked_stream_data(stream, datalen);
    return 0;
  }
  } // namespace
  
  void Handler::http_acked_stream_data(Stream *stream, uint64_t datalen) {
    stream->http_acked_stream_data(datalen);
  
    if (stream->dynresp && stream->dynbuflen < MAX_DYNBUFLEN - 16384) {
      if (auto rv = nghttp3_conn_resume_stream(httpconn_, stream->stream_id);
          rv != 0) {
        // TODO Handle error
        std::cerr << "nghttp3_conn_resume_stream: " << nghttp3_strerror(rv)
                  << std::endl;
      }
    }
  }
  
  namespace {
  int http_stream_close(nghttp3_conn *conn, int64_t stream_id,
                        uint64_t app_error_code, void *conn_user_data,
                        void *stream_user_data) {
    auto h = static_cast<Handler *>(conn_user_data);
    h->http_stream_close(stream_id, app_error_code);
    return 0;
  }
  } // namespace
  
  void Handler::http_stream_close(int64_t stream_id, uint64_t app_error_code) {
    auto it = streams_.find(stream_id);
    if (it == std::end(streams_)) {
      return;
    }
  
    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);
    }
  }
  
  namespace {
  int http_send_stop_sending(nghttp3_conn *conn, 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->http_send_stop_sending(stream_id, app_error_code) != 0) {
      return NGHTTP3_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::http_send_stop_sending(int64_t stream_id,
                                      uint64_t app_error_code) {
    if (auto rv =
            ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
        rv != 0) {
      std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
                << std::endl;
      if (rv == NGTCP2_ERR_STREAM_NOT_FOUND) {
        return 0;
      }
      return -1;
    }
    return 0;
  }
  
  namespace {
  int http_reset_stream(nghttp3_conn *conn, 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->http_reset_stream(stream_id, app_error_code) != 0) {
      return NGHTTP3_ERR_CALLBACK_FAILURE;
    }
    return 0;
  }
  } // namespace
  
  int Handler::http_reset_stream(int64_t stream_id, uint64_t app_error_code) {
    if (auto rv =
            ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code);
        rv != 0) {
      std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
                << std::endl;
      if (rv == NGTCP2_ERR_STREAM_NOT_FOUND) {
        return 0;
      }
      return -1;
    }
    return 0;
  }
  
  int Handler::setup_httpconn() {
    if (httpconn_) {
      return 0;
    }
  
    if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) {
      std::cerr << "peer does not allow at least 3 unidirectional streams."
                << std::endl;
      return -1;
    }
  
    nghttp3_callbacks callbacks{
        ::http_acked_stream_data, // acked_stream_data
        ::http_stream_close,
        ::http_recv_data,
        ::http_deferred_consume,
        ::http_begin_request_headers,
        ::http_recv_request_header,
        ::http_end_request_headers,
        nullptr, // begin_trailers
        nullptr, // recv_trailer
        nullptr, // end_trailers
        ::http_send_stop_sending,
        ::http_end_stream,
        ::http_reset_stream,
    };
    nghttp3_settings settings;
    nghttp3_settings_default(&settings);
    settings.qpack_max_table_capacity = 4096;
    settings.qpack_blocked_streams = 100;
  
    auto mem = nghttp3_mem_default();
  
    if (auto rv =
            nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this);
        rv != 0) {
      std::cerr << "nghttp3_conn_server_new: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    ngtcp2_transport_params params;
    ngtcp2_conn_get_local_transport_params(conn_, &params);
  
    nghttp3_conn_set_max_client_streams_bidi(httpconn_,
                                             params.initial_max_streams_bidi);
  
    int64_t ctrl_stream_id;
  
    if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr);
        rv != 0) {
      std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id);
        rv != 0) {
      std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (!config.quiet) {
      fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id);
    }
  
    int64_t qpack_enc_stream_id, qpack_dec_stream_id;
  
    if (auto rv =
            ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr);
        rv != 0) {
      std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (auto rv =
            ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr);
        rv != 0) {
      std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id,
                                                  qpack_dec_stream_id);
        rv != 0) {
      std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
  
    if (!config.quiet) {
      fprintf(stderr,
              "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n",
              qpack_enc_stream_id, qpack_dec_stream_id);
    }
  
    return 0;
  }
  
  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) {
    if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) {
      std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv)
                << std::endl;
      return -1;
    }
    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
        ::stream_reset,
        ::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,
        stream_stop_sending,
    };
  
    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.max_window = config.max_window;
    settings.max_stream_window = config.max_stream_window;
    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 = config.max_streams_uni;
    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{{local_addr.len, const_cast<sockaddr *>(&local_addr.su.sa)},
                    {salen, const_cast<sockaddr *>(sa)},
                    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{{local_addr.len, const_cast<sockaddr *>(&local_addr.su.sa)},
                    {salen, const_cast<sockaddr *>(sa)},
                    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() {
    std::array<nghttp3_vec, 16> vec;
    PathStorage path, prev_path;
    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_);
  
    if (config.cc_algo != NGTCP2_CC_ALGO_BBR) {
      /* 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;
      int fin = 0;
      nghttp3_ssize sveccnt = 0;
  
      if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) {
        sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin,
                                             vec.data(), vec.size());
        if (sveccnt < 0) {
          std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(sveccnt)
                    << std::endl;
          last_error_ = quic_err_app(sveccnt);
          return handle_error();
        }
      }
  
      ngtcp2_ssize ndatalen;
      auto v = vec.data();
      auto vcnt = static_cast<size_t>(sveccnt);
  
      uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
      if (fin) {
        flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
      }
  
      auto nwrite = ngtcp2_conn_writev_stream(
          conn_, &path.path, &pi, bufpos, max_udp_payload_size, &ndatalen, flags,
          stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
      if (nwrite < 0) {
        switch (nwrite) {
        case NGTCP2_ERR_STREAM_DATA_BLOCKED:
          assert(ndatalen == -1);
          if (auto rv = nghttp3_conn_block_stream(httpconn_, stream_id);
              rv != 0) {
            std::cerr << "nghttp3_conn_block_stream: " << nghttp3_strerror(rv)
                      << std::endl;
            last_error_ = quic_err_app(rv);
            return handle_error();
          }
          continue;
        case NGTCP2_ERR_STREAM_SHUT_WR:
          assert(ndatalen == -1);
          if (auto rv = nghttp3_conn_shutdown_stream_write(httpconn_, stream_id);
              rv != 0) {
            std::cerr << "nghttp3_conn_shutdown_stream_write: "
                      << nghttp3_strerror(rv) << std::endl;
            last_error_ = quic_err_app(rv);
            return handle_error();
          }
          continue;
        case NGTCP2_ERR_WRITE_MORE:
          assert(ndatalen >= 0);
          if (auto rv =
                  nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
              rv != 0) {
            std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
                      << std::endl;
            last_error_ = quic_err_app(rv);
            return handle_error();
          }
          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) {
        if (auto rv =
                nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
            rv != 0) {
          std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
                    << std::endl;
          last_error_ = quic_err_app(rv);
          return handle_error();
        }
      }
  
      if (nwrite == 0) {
        if (bufpos - buf.data()) {
          server_->send_packet(*static_cast<Endpoint *>(prev_path.path.user_data),
                               prev_path.path.local, prev_path.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_path.path, &path.path);
        prev_ecn = pi.ecn;
      } else if (!ngtcp2_path_eq(&prev_path.path, &path.path) ||
                 prev_ecn != pi.ecn) {
        server_->send_packet(*static_cast<Endpoint *>(prev_path.path.user_data),
                             prev_path.path.local, prev_path.path.remote,
                             prev_ecn, buf.data(), bufpos - buf.data() - nwrite,
                             max_udp_payload_size);
  
        server_->send_packet(*static_cast<Endpoint *>(path.path.user_data),
                             path.path.local, path.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 *>(path.path.user_data),
                             path.path.local, path.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 *>(path.path.user_data),
                           path.path.local, path.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);
  
    PathStorage path;
    ngtcp2_pkt_info pi;
    if (last_error_.type == QUICErrorType::Transport) {
      auto n = ngtcp2_conn_write_connection_close(
          conn_, &path.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
          last_error_.code, 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_, &path.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
          last_error_.code, 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_);
  }
  
  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);
    }
  
    if (!httpconn_) {
      return 0;
    }
  
    auto nconsumed = nghttp3_conn_read_stream(
        httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
    if (nconsumed < 0) {
      std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
                << std::endl;
      last_error_ = quic_err_app(nconsumed);
      return -1;
    }
  
    ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
    ngtcp2_conn_extend_max_offset(conn_, nconsumed);
  
    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;
    }
  
    if (httpconn_) {
      if (app_error_code == 0) {
        app_error_code = NGHTTP3_H3_NO_ERROR;
      }
      auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code);
      switch (rv) {
      case 0:
        break;
      case NGHTTP3_ERR_STREAM_NOT_FOUND:
        if (ngtcp2_is_bidi_stream(stream_id)) {
          assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
          ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
        }
        break;
      default:
        std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv)
                  << std::endl;
        last_error_ = quic_err_app(rv);
        return -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);
  }
  
  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 (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
                     static_cast<socklen_t>(sizeof(val))) == -1) {
        close(fd);
        continue;
      }
  
      fd_set_recv_ecn(fd, rp->ai_family);
  
      if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
        break;
      }
  
      close(fd);
    }
  
    if (!rp) {
      std::cerr << "Could not bind" << std::endl;
      return -1;
    }
  
    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;
  
    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 (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);
  
    if (bind(fd, &addr.su.sa, addr.len) == -1) {
      std::cerr << "bind: " << strerror(errno) << std::endl;
      close(fd);
      return -1;
    }
  
    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{local_addr.len, const_cast<sockaddr *>(&local_addr.su.sa)};
    ngtcp2_addr raddr{salen, const_cast<sockaddr *>(sa)};
  
    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{local_addr.len, const_cast<sockaddr *>(&local_addr.su.sa)};
    ngtcp2_addr raddr{salen, const_cast<sockaddr *>(sa)};
  
    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);
    if (nwrite < 0) {
      std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl;
      return -1;
    }
  
    buf.push(nwrite);
  
    ngtcp2_addr laddr{local_addr.len, const_cast<sockaddr *>(&local_addr.su.sa)};
    ngtcp2_addr raddr{salen, const_cast<sockaddr *>(sa)};
  
    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) {
      if (!config.quiet) {
        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_window = 6_m;
    config.max_stream_window = 6_m;
    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;
  }
  } // 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)
                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-window=<SIZE>
                Maximum connection-level flow  control window size.  The
                window auto-tuning is enabled if nonzero value is given,
                and window size is scaled up to this value.
                Default: )"
              << util::format_uint_iec(config.max_window) << R"(
    --max-stream-window=<SIZE>
                Maximum stream-level flow control window size.  The
                window auto-tuning is enabled if nonzero value is given,
                and window size is scaled up to this value.
                Default: )"
              << util::format_uint_iec(config.max_stream_window) << R"(
    --max-gso-dgrams=<N>
                Maximum  number of  UDP  datagrams that  are  sent in  a
                single GSO sendmsg call.
                Default: )"
              << config.max_gso_dgrams << 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-window", required_argument, &flag, 23},
          {"max-stream-window", required_argument, &flag, 24},
          {"max-gso-dgrams", required_argument, &flag, 25},
          {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;
          }
          std::cerr << "cc: specify cubic, reno, or bbr" << 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 23:
          // --max-window
          if (auto n = util::parse_uint_iec(optarg); !n) {
            std::cerr << "max-window: invalid argument" << std::endl;
            exit(EXIT_FAILURE);
          } else {
            config.max_window = *n;
          }
          break;
        case 24:
          // --max-stream-window
          if (auto n = util::parse_uint_iec(optarg); !n) {
            std::cerr << "max-stream-window: invalid argument" << std::endl;
            exit(EXIT_FAILURE);
          } else {
            config.max_stream_window = *n;
          }
          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;
        }
        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::H3) != 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;
  }
  