Commit e5cb867504e for nodejs

commit e5cb867504ee3f422cb3731128dcddc18e0ab144
Author: Node.js GitHub Bot <github-bot@iojs.org>
Date:   Wed Nov 11 11:18:43 2026 -0500

    deps: update ngtcp2 to 1.26.0

    PR-URL: https://github.com/nodejs/node/pull/67257
    Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>

diff --git a/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c b/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c
index 06712907754..8dd5cd126b3 100644
--- a/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c
+++ b/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c
@@ -411,6 +411,7 @@ static void crypto_buf_write(crypto_buf *cbuf, const uint8_t *data,

 struct ngtcp2_crypto_ossl_ctx {
   SSL *ssl;
+  ngtcp2_encryption_level rx_level;
   ngtcp2_encryption_level tx_level;
   crypto_buf *crypto_head, *crypto_read, *crypto_write;
   size_t crypto_head_released;
@@ -425,6 +426,7 @@ int ngtcp2_crypto_ossl_ctx_new(ngtcp2_crypto_ossl_ctx **possl_ctx, SSL *ssl) {
   }

   ossl_ctx->ssl = ssl;
+  ossl_ctx->rx_level = NGTCP2_ENCRYPTION_LEVEL_INITIAL;
   ossl_ctx->tx_level = NGTCP2_ENCRYPTION_LEVEL_INITIAL;
   ossl_ctx->crypto_head = ossl_ctx->crypto_read = ossl_ctx->crypto_write = NULL;
   ossl_ctx->crypto_head_released = 0;
@@ -962,7 +964,10 @@ int ngtcp2_crypto_read_write_crypto_data(
   SSL *ssl = ossl_ctx->ssl;
   int rv;
   int err;
-  (void)encryption_level;
+
+  if (encryption_level != ossl_ctx->rx_level) {
+    return -1;
+  }

   if (crypto_ossl_ctx_write_crypto_data(ossl_ctx, data, datalen) != 0) {
     return -1;
@@ -1128,7 +1133,10 @@ static int ossl_yield_secret(SSL *ssl, uint32_t ossl_level, int direction,
       return 0;
     }

-    ossl_ctx->tx_level = level;
+    /* CRYPTO data is never sent in 0-RTT packet. */
+    if (level != NGTCP2_ENCRYPTION_LEVEL_0RTT) {
+      ossl_ctx->tx_level = level;
+    }

     return 1;
   }
@@ -1138,6 +1146,11 @@ static int ossl_yield_secret(SSL *ssl, uint32_t ossl_level, int direction,
     return 0;
   }

+  /* This check is probably not needed, but just in case */
+  if (level != NGTCP2_ENCRYPTION_LEVEL_0RTT) {
+    ossl_ctx->rx_level = level;
+  }
+
   return 1;
 }

diff --git a/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c b/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c
index af934bf1046..e21c302f939 100644
--- a/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c
+++ b/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c
@@ -387,9 +387,11 @@ int ngtcp2_crypto_read_write_crypto_data(
   size_t i;
   int rv;

-  ptls_buffer_init(&sendbuf, (void *)"", 0);
+  if (epoch != ptls_get_read_epoch(cptls->ptls)) {
+    return -1;
+  }

-  assert(datalen == 0 || epoch == ptls_get_read_epoch(cptls->ptls));
+  ptls_buffer_init(&sendbuf, (void *)"", 0);

   rv = ptls_handle_message(cptls->ptls, &sendbuf, epoch_offsets, epoch, data,
                            datalen, &cptls->handshake_properties);
diff --git a/deps/ngtcp2/ngtcp2/crypto/shared.c b/deps/ngtcp2/ngtcp2/crypto/shared.c
index 925461e2397..2ee561a8689 100644
--- a/deps/ngtcp2/ngtcp2/crypto/shared.c
+++ b/deps/ngtcp2/ngtcp2/crypto/shared.c
@@ -236,6 +236,35 @@ int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, uint32_t version,
   return 0;
 }

+/*
+ * crypto_set_remote_transport_params retrieves remote QUIC transport
+ * parameters from |tls| and sets it to |conn|.  Some TLS backends set
+ * them to |conn| from their own TLS extension callback instead, which
+ * is never called if a remote endpoint omits the extension.  This
+ * function fails if they have not been set to |conn| after all.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+static int crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  const ngtcp2_transport_params *params;
+
+  if (ngtcp2_crypto_set_remote_transport_params(conn, tls) != 0) {
+    return -1;
+  }
+
+  params = ngtcp2_conn_get_remote_transport_params2(conn);
+
+  /* Client might have the remembered 0-RTT transport parameters, but
+     they never include initial_scid which is mandatory in the ones
+     received from a remote endpoint. */
+  if (!params || !params->initial_scid_present) {
+    ngtcp2_conn_set_tls_error(conn, NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM);
+    return -1;
+  }
+
+  return 0;
+}
+
 int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key,
                                             uint8_t *iv, uint8_t *hp_key,
                                             ngtcp2_encryption_level level,
@@ -282,7 +311,7 @@ int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key,
   case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
     if (ngtcp2_conn_is_server2(conn) &&
         !ngtcp2_conn_get_negotiated_version2(conn)) {
-      rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
+      rv = crypto_set_remote_transport_params(conn, tls);
       if (rv != 0) {
         return -1;
       }
@@ -340,7 +369,7 @@ int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key,
     break;
   case NGTCP2_ENCRYPTION_LEVEL_1RTT:
     if (!ngtcp2_conn_is_server2(conn)) {
-      rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
+      rv = crypto_set_remote_transport_params(conn, tls);
       if (rv != 0) {
         goto fail;
       }
@@ -443,7 +472,7 @@ int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key,
   case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
     if (ngtcp2_conn_is_server2(conn) &&
         !ngtcp2_conn_get_negotiated_version2(conn)) {
-      rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
+      rv = crypto_set_remote_transport_params(conn, tls);
       if (rv != 0) {
         return -1;
       }
diff --git a/deps/ngtcp2/ngtcp2/examples/client.cc b/deps/ngtcp2/ngtcp2/examples/client.cc
index 0950ebf3a4f..ec93de159cd 100644
--- a/deps/ngtcp2/ngtcp2/examples/client.cc
+++ b/deps/ngtcp2/ngtcp2/examples/client.cc
@@ -1721,7 +1721,8 @@ std::expected<void, Error> Client::make_stream_early() {
 void Client::on_extend_max_streams() {
   int64_t stream_id;

-  if ((config.delay_stream && !handshake_confirmed_) ||
+  if (config.requests.empty() ||
+      (config.delay_stream && !handshake_confirmed_) ||
       ev_is_active(&delay_stream_timer_)) {
     return;
   }
diff --git a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc
index ed072c8058f..bbfa33bfa6e 100644
--- a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc
+++ b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc
@@ -82,6 +82,10 @@ ProtoCodec::on_stream_close(int64_t stream_id,
     return {};
   }

+  if (auto stream = client_->find_stream(stream_id); stream) {
+    sendq_.erase(stream);
+  }
+
   if (!config.quiet) {
     std::println(stderr,
                  "HTTP stream {:#x} closed with error codes (RX:{}, TX:{})",
diff --git a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h
index 9e8e30b49af..a4a92350cc1 100644
--- a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h
+++ b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h
@@ -63,7 +63,7 @@ public:
   std::expected<void, Error> extend_max_stream_data(int64_t stream_id,
                                                     uint64_t max_data);

-  void early_data_rejected() {}
+  void early_data_rejected() { sendq_.clear(); }

   std::expected<void, Error>
   on_stream_close(int64_t stream_id, std::optional<uint64_t> rx_app_error_code,
diff --git a/deps/ngtcp2/ngtcp2/examples/server.cc b/deps/ngtcp2/ngtcp2/examples/server.cc
index 79896b539a8..1f4bfd19e76 100644
--- a/deps/ngtcp2/ngtcp2/examples/server.cc
+++ b/deps/ngtcp2/ngtcp2/examples/server.cc
@@ -31,6 +31,8 @@
 #include <memory>
 #include <fstream>
 #include <iomanip>
+#include <limits>
+#include <utility>

 #include <unistd.h>
 #include <getopt.h>
@@ -216,6 +218,11 @@ Stream::open_file(const std::filesystem::path &path) {
     close(fd);
   } else {
     fe.fd = fd;
+    // A file larger than the address space cannot be mapped.
+    if (std::cmp_greater(st.st_size, std::numeric_limits<size_t>::max())) {
+      close(fd);
+      return std::unexpected{Error::INTEGER_OVERFLOW};
+    }
     fe.len = static_cast<size_t>(st.st_size);
     if (fe.len) {
       fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0);
@@ -321,9 +328,7 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
   }

   if (auto rv = h->handle_expiry(); !rv) {
-    if (rv.error() == Error::CLOSE_WAIT) {
-      ev_timer_stop(loop, w);
-    } else {
+    if (rv.error() != Error::CLOSE_WAIT) {
       s->remove(h);
     }

@@ -773,6 +778,7 @@ Handler::init(const Endpoint &ep, const Address &local_addr,
   settings.handshake_timeout = config.handshake_timeout;
   settings.no_pmtud = config.no_pmtud;
   settings.ack_thresh = config.ack_thresh;
+  settings.write_stream_data_thresh = config.write_stream_data_thresh;
   if (config.max_udp_payload_size) {
     settings.max_tx_udp_payload_size = config.max_udp_payload_size;
     settings.no_tx_udp_payload_size_shaping = 1;
@@ -1705,7 +1711,9 @@ void Server::on_read(const Endpoint &ep) {
           stderr,
           "Received packet: local={} remote={} if={} ecn={:#x} {} bytes",
           util::straddr(*local_addr), util::straddr(remote_addr),
-          if_indextoname(local_addr->ifindex, ifname.data()), pi.ecn, datalen);
+          if_indextoname(local_addr->ifindex, ifname.data()) ? ifname.data()
+                                                             : "(unknown)",
+          pi.ecn, datalen);
       }

       // Packets less than 21 bytes never be a valid QUIC packet.
@@ -1735,6 +1743,11 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr,
                                                   NGTCP2_SV_SCIDLEN);
           rv) {
   case 0:
+    if ((data[0] & 0x80U) && vc.version == 0) {
+      // Version Negotiation packet was received.
+      return;
+    }
+
     break;
   case NGTCP2_ERR_VERSION_NEGOTIATION:
     (void)send_version_negotiation(vc.version, {vc.scid, vc.scidlen},
@@ -2658,6 +2671,15 @@ Options:
               to send  per an event  loop in a single  connection.  It
               defaults  to 0,  which means  it is  not limited  by the
               configuration.
+  --write-stream-data-thresh=<SIZE>
+              The  threshold for  the length  of STREAM  frame payload
+              that the  local endpoint sends.   If there is  data more
+              than this value  to send, and the flow  control does not
+              allow sending  more than  this length,  the data  is not
+              sent,  waiting for  the  window update  from the  remote
+              endpoint.
+              Default: )"
+            << config.write_stream_data_thresh << R"(
   -h, --help  Display this help and exit.

 ---
@@ -2732,6 +2754,7 @@ int main(int argc, char **argv) {
       {"no-gso", no_argument, &flag, 35},
       {"show-stat", no_argument, &flag, 36},
       {"gso-burst", required_argument, &flag, 37},
+      {"write-stream-data-thresh", required_argument, &flag, 38},
       {},
     };

@@ -3163,6 +3186,25 @@ int main(int argc, char **argv) {

         break;
       }
+      case 38: {
+        // --write-stream-data-thresh
+        auto n = util::parse_uint_iec(optarg);
+        if (!n) {
+          std::println(stderr, "write-stream-data-thresh: invalid argument");
+          exit(EXIT_FAILURE);
+        }
+
+        if (auto max = std::numeric_limits<size_t>::max(); *n > max) {
+          std::println(
+            stderr,
+            "write-stream-data-thresh: must be less than or equal to {}", max);
+          exit(EXIT_FAILURE);
+        }
+
+        config.write_stream_data_thresh = static_cast<size_t>(*n);
+
+        break;
+      }
       }
       break;
     default:
diff --git a/deps/ngtcp2/ngtcp2/examples/server.h b/deps/ngtcp2/ngtcp2/examples/server.h
index 8a71a12a2e7..3d7a47fc420 100644
--- a/deps/ngtcp2/ngtcp2/examples/server.h
+++ b/deps/ngtcp2/ngtcp2/examples/server.h
@@ -68,7 +68,7 @@ enum FileEntryFlag {
 };

 struct FileEntry {
-  uint64_t len{};
+  size_t len{};
   void *map{};
   int fd{};
   uint8_t flags{};
diff --git a/deps/ngtcp2/ngtcp2/examples/server_base.h b/deps/ngtcp2/ngtcp2/examples/server_base.h
index 9438d11f8bd..af0660fe7ae 100644
--- a/deps/ngtcp2/ngtcp2/examples/server_base.h
+++ b/deps/ngtcp2/ngtcp2/examples/server_base.h
@@ -169,6 +169,9 @@ struct Config {
   // gso_burst is the number of packets to aggregate in GSO.  0 means
   // it is not limited by the configuration.
   size_t gso_burst{};
+  // write_stream_data_thresh is the threshold for the length of
+  // STREAM frame payload that the local endpoint sends.
+  size_t write_stream_data_thresh{255};
 };

 struct HTTPHeader {
diff --git a/deps/ngtcp2/ngtcp2/examples/sim_test.cc b/deps/ngtcp2/ngtcp2/examples/sim_test.cc
index b7e6d69e36d..23c3282fa14 100644
--- a/deps/ngtcp2/ngtcp2/examples/sim_test.cc
+++ b/deps/ngtcp2/ngtcp2/examples/sim_test.cc
@@ -130,8 +130,8 @@ void test_sim_unistream(void) {

     assert_true(rv.has_value());
     assert_true(svapp.is_all_bytes_sent());
-    assert_uint64(link.compute_expected_goodput(link.delay * 2), <=,
-                  svapp.compute_goodput());
+    assert_lte(link.compute_expected_goodput(link.delay * 2),
+               svapp.compute_goodput());
   }
 }

diff --git a/deps/ngtcp2/ngtcp2/examples/siphash_test.cc b/deps/ngtcp2/ngtcp2/examples/siphash_test.cc
index 54f300444ef..77ac585ee4e 100644
--- a/deps/ngtcp2/ngtcp2/examples/siphash_test.cc
+++ b/deps/ngtcp2/ngtcp2/examples/siphash_test.cc
@@ -62,7 +62,7 @@ void test_siphash(void) {
   std::array<uint8_t, 15> input;
   std::iota(std::ranges::begin(input), std::ranges::end(input), 0);

-  assert_uint64(0xA129CA6149BE45E5ULL, ==, siphash24(key, input));
+  assert_eq(0xA129CA6149BE45E5ULL, siphash24(key, input));
 }

 void test_siphash_vector(void) {
@@ -91,7 +91,7 @@ void test_siphash_vector(void) {
       expect = byteswap(expect);
     }

-    assert_uint64(expect, ==, h);
+    assert_eq(expect, h);
   }
 }

diff --git a/deps/ngtcp2/ngtcp2/examples/siphash_test.h b/deps/ngtcp2/ngtcp2/examples/siphash_test.h
index c356d2e6b00..11a867f9450 100644
--- a/deps/ngtcp2/ngtcp2/examples/siphash_test.h
+++ b/deps/ngtcp2/ngtcp2/examples/siphash_test.h
@@ -32,7 +32,7 @@

 #define MUNIT_ENABLE_ASSERT_ALIASES

-#include "munit.h"
+#include "munitxx.h"

 namespace ngtcp2 {

diff --git a/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/client.py b/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/client.py
index c22f661a782..4a631627099 100644
--- a/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/client.py
+++ b/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/client.py
@@ -107,7 +107,7 @@ class ClientRun:
     def assert_verify_cert_handshake(self):
         c_hs, s_hs = self.norm_exp(
             "ServerHello:EncryptedExtensions:CertificateRequest:(Compressed)?Certificate:CertificateVerify:Finished",
-            "ClientHello:Certificate:CertificateVerify:Finished")
+            "ClientHello:(Compressed)?Certificate:CertificateVerify:Finished")
         self._assert_hs(c_hs, s_hs)


diff --git a/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/tls.py b/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/tls.py
index 414b94d38e5..ded059416b6 100644
--- a/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/tls.py
+++ b/deps/ngtcp2/ngtcp2/examples/tests/ngtcp2test/tls.py
@@ -842,6 +842,31 @@ class Certificate(HSRecord):
             f'{ind}  certificate_list: \n' + '\n'.join(
                 [self._enxtry_text(e, indent+4) for e in self._cert_entries])

+class CompressedCertificate(HSRecord):
+
+    def __init__(self, hsid: int, name: str, data):
+        super().__init__(hsid=hsid, name=name, data=data)
+        d = data
+        d, algorithm = _get_field(d, 2)
+        self._algorithm = int.from_bytes(bytearray(algorithm), byteorder='big')
+        d, uncompressed_length = _get_field(d, 3)
+        self._uncompressed_length = int.from_bytes(bytearray(uncompressed_length), byteorder='big')
+        d, self._compressed_certificate_message = _get_len_field(d, 3)
+        assert len(d) == 0
+
+    def to_json(self):
+        jdata = super().to_json()
+        jdata['algorithm'] = self._algorithm
+        jdata['uncompressed_length'] = self._uncompressed_length
+        jdata['compressed_certificate_message'] = binascii.hexlify(self._compressed_certificate_message).decode()
+        return jdata
+
+    def to_text(self, indent: int = 0):
+        ind = ' ' * (indent + 2)
+        return super().to_text(indent=indent) + '\n'\
+            f'{ind}  algorithm: {self._algorithm}\n'\
+            f'{ind}  uncompressed_length: {self._uncompressed_length}\n'\
+            f'{ind}  compressed_certificate_message: {binascii.hexlify(self._compressed_certificate_message).decode()}'

 class SessionTicket(HSRecord):

@@ -901,7 +926,7 @@ class HandShake:
         (20, 'Finished', HSRecord),
         (22, 'CertificateStatus', HSRecord),
         (24, 'KeyUpdate', HSRecord),
-        (25, 'CompressedCertificate', HSRecord),
+        (25, 'CompressedCertificate', CompressedCertificate),
     ]
     RT_NAME_BY_ID = {}
     RT_CLS_BY_ID = {}
diff --git a/deps/ngtcp2/ngtcp2/examples/tests/test_04_clientcert.py b/deps/ngtcp2/ngtcp2/examples/tests/test_04_clientcert.py
index bde1b187fda..25b4029f13d 100644
--- a/deps/ngtcp2/ngtcp2/examples/tests/test_04_clientcert.py
+++ b/deps/ngtcp2/ngtcp2/examples/tests/test_04_clientcert.py
@@ -49,9 +49,13 @@ class TestClientCert:
         creqs = [r for r in cr.handshake if r.hsid == 13]  # CertificateRequest
         assert len(creqs) == 1
         creq = creqs[0].to_json()
-        certs = [r for r in cr.server.handshake if r.hsid == 11]  # Certificate
+        certs = [r for r in cr.server.handshake if r.hsid == 11 or r.hsid == 25]  # Certificate or CompressedCertificate
         assert len(certs) == 1
         crec = certs[0].to_json()
-        assert len(crec['certificate_list']) == 1
-        assert creq['context'] == crec['context']
+        if 'certificate_list' in crec:
+            cert_list = crec['certificate_list']
+            assert len(cert_list) == 1
+            assert creq['context'] == crec['context']
+        else:
+            assert 'compressed_certificate_message' in crec
         # TODO: check that GET indeed gave a response
diff --git a/deps/ngtcp2/ngtcp2/examples/tls_client_context_picotls.cc b/deps/ngtcp2/ngtcp2/examples/tls_client_context_picotls.cc
index d5ee705be0a..6cc632f0196 100644
--- a/deps/ngtcp2/ngtcp2/examples/tls_client_context_picotls.cc
+++ b/deps/ngtcp2/ngtcp2/examples/tls_client_context_picotls.cc
@@ -36,7 +36,8 @@
 extern Config config;

 namespace {
-int save_ticket_cb(ptls_save_ticket_t *self, ptls_t *ptls, ptls_iovec_t input) {
+int save_ticket_cb(ptls_save_ticket_t *self, ptls_t *ptls, ptls_iovec_t input,
+                   const ptls_save_ticket_properties_t *ticket_prop) {
   auto conn_ref =
     static_cast<ngtcp2_crypto_conn_ref *>(*ptls_get_data_ptr(ptls));
   auto c = static_cast<ClientBase *>(conn_ref->user_data);
diff --git a/deps/ngtcp2/ngtcp2/examples/util_test.cc b/deps/ngtcp2/ngtcp2/examples/util_test.cc
index 14a7bd05d92..ab2ce1266d1 100644
--- a/deps/ngtcp2/ngtcp2/examples/util_test.cc
+++ b/deps/ngtcp2/ngtcp2/examples/util_test.cc
@@ -92,323 +92,112 @@ std::expected<void, Error> generate_secure_random(std::span<uint8_t> data) {
 } // namespace util

 void test_util_format_durationf() {
-  assert_stdstring_equal("0ns", util::format_durationf(0));
-  assert_stdstring_equal("999ns", util::format_durationf(999));
-  assert_stdstring_equal("1.00us", util::format_durationf(1000));
-  assert_stdstring_equal("1.00us", util::format_durationf(1004));
-  assert_stdstring_equal("1.00us", util::format_durationf(1005));
-  assert_stdstring_equal("1.02us", util::format_durationf(1015));
-  assert_stdstring_equal("2.00us", util::format_durationf(1999));
-  assert_stdstring_equal("1.00ms", util::format_durationf(999999));
-  assert_stdstring_equal("3.50ms", util::format_durationf(3500111));
-  assert_stdstring_equal("9999.99s", util::format_durationf(9999990000000ULL));
+  assert_eq("0ns", util::format_durationf(0));
+  assert_eq("999ns", util::format_durationf(999));
+  assert_eq("1.00us", util::format_durationf(1000));
+  assert_eq("1.00us", util::format_durationf(1004));
+  assert_eq("1.00us", util::format_durationf(1005));
+  assert_eq("1.02us", util::format_durationf(1015));
+  assert_eq("2.00us", util::format_durationf(1999));
+  assert_eq("1.00ms", util::format_durationf(999999));
+  assert_eq("3.50ms", util::format_durationf(3500111));
+  assert_eq("9999.99s", util::format_durationf(9999990000000ULL));
 }

 void test_util_format_uint() {
-  assert_stdstring_equal("0"s, util::format_uint(0U));
-  assert_stdstring_equal("18446744073709551615"s,
-                         util::format_uint(18446744073709551615ULL));
+  assert_eq("0", util::format_uint(0U));
+  assert_eq("18446744073709551615", util::format_uint(18446744073709551615ULL));
 }

 void test_util_format_uint_iec() {
-  assert_stdstring_equal("0"s, util::format_uint_iec(0U));
-  assert_stdstring_equal("1023"s, util::format_uint_iec((1U << 10) - 1));
-  assert_stdstring_equal("1K"s, util::format_uint_iec(1U << 10));
-  assert_stdstring_equal("1M"s, util::format_uint_iec(1U << 20));
-  assert_stdstring_equal("1G"s, util::format_uint_iec(1U << 30));
-  assert_stdstring_equal(
-    "18446744073709551615"s,
-    util::format_uint_iec(std::numeric_limits<uint64_t>::max()));
-  assert_stdstring_equal("1025K"s,
-                         util::format_uint_iec((1U << 20) + (1U << 10)));
+  assert_eq("0", util::format_uint_iec(0U));
+  assert_eq("1023", util::format_uint_iec((1U << 10) - 1));
+  assert_eq("1K", util::format_uint_iec(1U << 10));
+  assert_eq("1M", util::format_uint_iec(1U << 20));
+  assert_eq("1G", util::format_uint_iec(1U << 30));
+  assert_eq("18446744073709551615"s,
+            util::format_uint_iec(std::numeric_limits<uint64_t>::max()));
+  assert_eq("1025K", util::format_uint_iec((1U << 20) + (1U << 10)));
 }

 void test_util_format_duration() {
-  assert_stdstring_equal("0ns", util::format_duration(0));
-  assert_stdstring_equal("999ns", util::format_duration(999));
-  assert_stdstring_equal("1us", util::format_duration(1000));
-  assert_stdstring_equal("1ms", util::format_duration(1000000));
-  assert_stdstring_equal("1s", util::format_duration(1000000000));
-  assert_stdstring_equal("1m", util::format_duration(60000000000ULL));
-  assert_stdstring_equal("1h", util::format_duration(3600000000000ULL));
-  assert_stdstring_equal(
-    "18446744073709551615ns",
-    util::format_duration(std::numeric_limits<uint64_t>::max()));
-  assert_stdstring_equal("61s", util::format_duration(61000000000ULL));
+  assert_eq("0ns", util::format_duration(0));
+  assert_eq("999ns", util::format_duration(999));
+  assert_eq("1us", util::format_duration(1000));
+  assert_eq("1ms", util::format_duration(1000000));
+  assert_eq("1s", util::format_duration(1000000000));
+  assert_eq("1m", util::format_duration(60000000000ULL));
+  assert_eq("1h", util::format_duration(3600000000000ULL));
+  assert_eq("18446744073709551615ns",
+            util::format_duration(std::numeric_limits<uint64_t>::max()));
+  assert_eq("61s", util::format_duration(61000000000ULL));
 }

 void test_util_parse_uint() {
-  {
-    auto res = util::parse_uint("0");
-    assert_true(res.has_value());
-    assert_uint64(0, ==, *res);
-  }
-  {
-    auto res = util::parse_uint("1");
-    assert_true(res.has_value());
-    assert_uint64(1, ==, *res);
-  }
-  {
-    auto res = util::parse_uint("18446744073709551615");
-    assert_true(res.has_value());
-    assert_uint64(18446744073709551615ULL, ==, *res);
-  }
-  {
-    auto res = util::parse_uint("18446744073709551616");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint("a");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint("1a");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint("");
-    assert_false(res.has_value());
-  }
+  assert_ok_eq(0, util::parse_uint("0"));
+  assert_ok_eq(1, util::parse_uint("1"));
+  assert_ok_eq(18446744073709551615ULL,
+               util::parse_uint("18446744073709551615"));
+  assert_err(Error::INTEGER_OVERFLOW, util::parse_uint("18446744073709551616"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint("a"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint("1a"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint(""));
 }

 void test_util_parse_uint_iec() {
-  {
-    auto res = util::parse_uint_iec("0");
-    assert_true(res.has_value());
-    assert_uint64(0, ==, *res);
-  }
-  {
-    auto res = util::parse_uint_iec("1023");
-    assert_true(res.has_value());
-    assert_uint64(1023, ==, *res);
-  }
-  {
-    auto res = util::parse_uint_iec("1K");
-    assert_true(res.has_value());
-    assert_uint64(1 << 10, ==, *res);
-  }
-  {
-    auto res = util::parse_uint_iec("1M");
-    assert_true(res.has_value());
-    assert_uint64(1 << 20, ==, *res);
-  }
-  {
-    auto res = util::parse_uint_iec("1G");
-    assert_true(res.has_value());
-    assert_uint64(1 << 30, ==, *res);
-  }
-  {
-    auto res = util::parse_uint_iec("11G");
-    assert_true(res.has_value());
-    assert_uint64((1ULL << 30) * 11, ==, *res);
-  }
-  {
-    auto res = util::parse_uint_iec("18446744073709551616");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint_iec("1x");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint_iec("1Gx");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint_iec("G");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_uint_iec("");
-    assert_false(res.has_value());
-  }
+  assert_ok_eq(0, util::parse_uint_iec("0"));
+  assert_ok_eq(1023, util::parse_uint_iec("1023"));
+  assert_ok_eq(1 << 10, util::parse_uint_iec("1K"));
+  assert_ok_eq(1 << 20, util::parse_uint_iec("1M"));
+  assert_ok_eq(1 << 30, util::parse_uint_iec("1G"));
+  assert_ok_eq((1ULL << 30) * 11, util::parse_uint_iec("11G"));
+  assert_err(Error::INTEGER_OVERFLOW,
+             util::parse_uint_iec("18446744073709551616"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint_iec("1x"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint_iec("1Gx"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint_iec("G"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_uint_iec(""));
 }

 void test_util_parse_duration() {
-  {
-    auto res = util::parse_duration("0");
-    assert_true(res.has_value());
-    assert_uint64(0, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1");
-    assert_true(res.has_value());
-    assert_uint64(NGTCP2_SECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("0ns");
-    assert_true(res.has_value());
-    assert_uint64(0, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1ns");
-    assert_true(res.has_value());
-    assert_uint64(1, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1us");
-    assert_true(res.has_value());
-    assert_uint64(NGTCP2_MICROSECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1ms");
-    assert_true(res.has_value());
-    assert_uint64(NGTCP2_MILLISECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1s");
-    assert_true(res.has_value());
-    assert_uint64(NGTCP2_SECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1m");
-    assert_true(res.has_value());
-    assert_uint64(60 * NGTCP2_SECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("1h");
-    assert_true(res.has_value());
-    assert_uint64(3600 * NGTCP2_SECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("2h");
-    assert_true(res.has_value());
-    assert_uint64(2 * 3600 * NGTCP2_SECONDS, ==, *res);
-  }
-  {
-    auto res = util::parse_duration("18446744073709551616");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_duration("1x");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_duration("1mx");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_duration("1mxy");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_duration("s");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_duration("ms");
-    assert_false(res.has_value());
-  }
-  {
-    auto res = util::parse_duration("");
-    assert_false(res.has_value());
-  }
+  assert_ok_eq(0, util::parse_duration("0"));
+  assert_ok_eq(NGTCP2_SECONDS, util::parse_duration("1"));
+  assert_ok_eq(0, util::parse_duration("0ns"));
+  assert_ok_eq(1, util::parse_duration("1ns"));
+  assert_ok_eq(NGTCP2_MICROSECONDS, util::parse_duration("1us"));
+  assert_ok_eq(NGTCP2_MILLISECONDS, util::parse_duration("1ms"));
+  assert_ok_eq(NGTCP2_SECONDS, util::parse_duration("1s"));
+  assert_ok_eq(60 * NGTCP2_SECONDS, util::parse_duration("1m"));
+  assert_ok_eq(3600 * NGTCP2_SECONDS, util::parse_duration("1h"));
+  assert_ok_eq(2 * 3600 * NGTCP2_SECONDS, util::parse_duration("2h"));
+  assert_err(Error::INTEGER_OVERFLOW,
+             util::parse_duration("18446744073709551616"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_duration("1x"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_duration("1mx"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_duration("1mxy"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_duration("s"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_duration("ms"));
+  assert_err(Error::INVALID_ARGUMENT, util::parse_duration(""));
 }

 void test_util_normalize_path() {
-  {
-    auto rv = util::normalize_path("/");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("//");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/foo");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/foo/bar/");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo/bar/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/foo/abc/../bar/");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo/bar/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/../foo/abc/../bar/");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo/bar/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/./foo/././abc///.././bar/./");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo/bar/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/foo/.");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/foo/./bar");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/foo/bar", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/foo/./../bar");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/bar", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/../../bar");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/bar", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path(std::string(1024, '/'));
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path(std::string(1025, '/'));
-
-    assert_false(rv);
-  }
-
-  {
-    auto rv = util::normalize_path("/..");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/", rv.value());
-  }
-
-  {
-    auto rv = util::normalize_path("/../../index.html");
-
-    assert_true(rv.has_value());
-    assert_stdstring_equal("/index.html", rv.value());
-  }
+  assert_ok_eq("/", util::normalize_path("/"));
+  assert_ok_eq("/", util::normalize_path("//"));
+  assert_ok_eq("/foo", util::normalize_path("/foo"));
+  assert_ok_eq("/foo/bar/", util::normalize_path("/foo/bar/"));
+  assert_ok_eq("/foo/bar/", util::normalize_path("/foo/abc/../bar/"));
+  assert_ok_eq("/foo/bar/", util::normalize_path("/../foo/abc/../bar/"));
+  assert_ok_eq("/foo/bar/",
+               util::normalize_path("/./foo/././abc///.././bar/./"));
+  assert_ok_eq("/foo/", util::normalize_path("/foo/."));
+  assert_ok_eq("/foo/bar", util::normalize_path("/foo/./bar"));
+  assert_ok_eq("/bar", util::normalize_path("/foo/./../bar"));
+  assert_ok_eq("/bar", util::normalize_path("/../../bar"));
+  assert_ok_eq("/", util::normalize_path(std::string(1024, '/')));
+  assert_err(Error::INVALID_ARGUMENT,
+             util::normalize_path(std::string(1025, '/')));
+  assert_ok_eq("/", util::normalize_path("/.."));
+  assert_ok_eq("/index.html", util::normalize_path("/../../index.html"));
 }

 void test_util_hexdump() {
@@ -522,15 +311,14 @@ void test_util_hexdump() {
     munit_log(MUNIT_LOG_INFO, t.title);

     auto f = tmpfile();
-    auto rv = util::hexdump(f, std::span{t.data});

-    assert_true(rv.has_value());
+    assert_ok(util::hexdump(f, std::span{t.data}));

     fseek(f, 0, SEEK_SET);
     auto nread = fread(buf, 1, sizeof(buf), f);
     buf[nread] = '\0';

-    assert_stdsv_equal(t.dump, buf);
+    assert_eq(t.dump, buf);

     fclose(f);
   }
@@ -539,58 +327,39 @@ void test_util_hexdump() {
 void test_util_format_hex() {
   auto a = std::to_array<uint8_t>({0xDE, 0xAD, 0xBE, 0xEF});

-  assert_stdstring_equal("deadbeef"s, util::format_hex(a));
-  assert_stdstring_equal("deadbeef"s, util::format_hex(0xDEADBEEF));
-  assert_stdstring_equal("beef"s, util::format_hex(a.data() + 2, 2));
+  assert_eq("deadbeef", util::format_hex(a));
+  assert_eq("deadbeef", util::format_hex(0xDEADBEEF));
+  assert_eq("beef", util::format_hex(a.data() + 2, 2));

   std::array<char, 64> buf;
-
-  assert_stdsv_equal(
-    "00"sv, (std::string_view{std::ranges::begin(buf),
-                              util::format_hex(static_cast<uint8_t>(0U),
-                                               std::ranges::begin(buf))}));
-  assert_stdsv_equal(
-    "ec"sv, (std::string_view{std::ranges::begin(buf),
-                              util::format_hex(static_cast<uint8_t>(0xECU),
-                                               std::ranges::begin(buf))}));
-  assert_stdsv_equal(
-    "00000000"sv,
-    (std::string_view{std::ranges::begin(buf),
-                      util::format_hex(0U, std::ranges::begin(buf))}));
-  assert_stdsv_equal(
-    "0000ab01"sv,
-    (std::string_view{std::ranges::begin(buf),
-                      util::format_hex(0xAB01U, std::ranges::begin(buf))}));
-  assert_stdsv_equal(
-    "deadbeefbaadf00d"sv,
-    (std::string_view{
-      std::ranges::begin(buf),
-      util::format_hex(0xDEADBEEFBAADF00DU, std::ranges::begin(buf))}));
-  assert_stdsv_equal(
-    "ffffffffffffffff"sv,
-    (std::string_view{std::ranges::begin(buf),
-                      util::format_hex(std::numeric_limits<uint64_t>::max(),
-                                       std::ranges::begin(buf))}));
+  auto b = std::ranges::begin(buf);
+
+  assert_eq("00",
+            std::string_view(b, util::format_hex(static_cast<uint8_t>(0U), b)));
+  assert_eq("ec", std::string_view(
+                    b, util::format_hex(static_cast<uint8_t>(0xECU), b)));
+  assert_eq("00000000", std::string_view(b, util::format_hex(0U, b)));
+  assert_eq("0000ab01", std::string_view(b, util::format_hex(0xAB01U, b)));
+  assert_eq("deadbeefbaadf00d",
+            std::string_view(b, util::format_hex(0xDEADBEEFBAADF00DU, b)));
+  assert_eq("ffffffffffffffff",
+            std::string_view(
+              b, util::format_hex(std::numeric_limits<uint64_t>::max(), b)));

   std::vector<char> char_vec;
   util::format_hex(a, std::back_inserter(char_vec));

-  assert_stdsv_equal("deadbeef"sv,
-                     (std::string_view{std::ranges::begin(char_vec),
-                                       std::ranges::end(char_vec)}));
+  assert_eq("deadbeef", as_string_view(char_vec));

   std::vector<uint8_t> uint8_vec;
   util::format_hex(a, std::back_inserter(uint8_vec));

-  assert_stdsv_equal(
-    "deadbeef"sv,
-    (std::string_view{reinterpret_cast<const char *>(uint8_vec.data()),
-                      uint8_vec.size()}));
+  assert_eq("deadbeef", as_string_view(uint8_vec));
 }

 void test_util_decode_hex() {
-  assert_stdstring_equal("\xDE\xAD\xBE\xEF"s, util::decode_hex("deadbeef"sv));
-  assert_stdstring_equal(""s, util::decode_hex(""sv));
+  assert_eq("\xDE\xAD\xBE\xEF", util::decode_hex("deadbeef"sv));
+  assert_eq("", util::decode_hex(""sv));
 }

 void test_util_is_hex_string() {
@@ -607,25 +376,41 @@ void test_util_is_hex_string() {
 }

 void test_util_split_str() {
-  assert_true((std::vector{"alpha"sv, "bravo"sv, "charlie"sv} ==
-               (util::split_str("alpha,bravo,charlie"sv) |
-                std::ranges::to<std::vector>())));
-  assert_true((std::vector{"alpha"sv, "bravo"sv, "charlie"sv} ==
-               (util::split_str("alpha bravo charlie"sv, ' ') |
-                std::ranges::to<std::vector>())));
-  assert_true((std::vector<std::string_view>{} ==
-               (util::split_str(""sv, ' ') | std::ranges::to<std::vector>())));
-  assert_true((std::vector{""sv, ""sv} ==
-               (util::split_str(","sv) | std::ranges::to<std::vector>())));
-  assert_true(
-    (std::vector{""sv, "alpha"sv, ""sv, ""sv, "bravo"sv, "charlie"sv, ""sv,
-                 ""sv} == (util::split_str(" alpha   bravo charlie  "sv, ' ') |
-                           std::ranges::to<std::vector>())));
+  assert_eq((std::vector{
+              "alpha"sv,
+              "bravo"sv,
+              "charlie"sv,
+            }),
+            util::split_str("alpha,bravo,charlie"sv) |
+              std::ranges::to<std::vector>());
+  assert_eq((std::vector{
+              "alpha"sv,
+              "bravo"sv,
+              "charlie"sv,
+            }),
+            util::split_str("alpha bravo charlie"sv, ' ') |
+              std::ranges::to<std::vector>());
+  assert_eq((std::vector<std::string_view>{}),
+            util::split_str(""sv, ' ') | std::ranges::to<std::vector>());
+  assert_eq((std::vector{""sv, ""sv}),
+            util::split_str(","sv) | std::ranges::to<std::vector>());
+  assert_eq((std::vector{
+              ""sv,
+              "alpha"sv,
+              ""sv,
+              ""sv,
+              "bravo"sv,
+              "charlie"sv,
+              ""sv,
+              ""sv,
+            }),
+            util::split_str(" alpha   bravo charlie  "sv, ' ') |
+              std::ranges::to<std::vector>());
 }

 void test_util_format_app_error_code() {
-  assert_stdstring_equal("(no error)", util::format_app_error_code({}));
-  assert_stdstring_equal("0xfe01", util::format_app_error_code(0xfe01));
+  assert_eq("(no error)", util::format_app_error_code({}));
+  assert_eq("0xfe01", util::format_app_error_code(0xfe01));
 }

 } // namespace ngtcp2
diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
index 9bc9a7b512e..51e62c29eec 100644
--- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
+++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
@@ -1861,7 +1861,8 @@ typedef enum ngtcp2_token_type {
 #define NGTCP2_SETTINGS_V2 2
 #define NGTCP2_SETTINGS_V3 3
 #define NGTCP2_SETTINGS_V4 4
-#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_V4
+#define NGTCP2_SETTINGS_V5 5
+#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_V5

 /**
  * @struct
@@ -1913,7 +1914,7 @@ typedef struct ngtcp2_settings {
    * :member:`token_type` field.
    *
    * Client sets this field if it intends to send token in its Initial
-   * packet.
+   * packet.  A token longer than 512 bytes is ignored.
    *
    * `ngtcp2_conn_server_new` and `ngtcp2_conn_client_new` make a copy
    * of token.
@@ -2087,11 +2088,12 @@ typedef struct ngtcp2_settings {
   /* The following fields have been added since NGTCP2_SETTINGS_V3. */
   /**
    * :member:`glitch_ratelim_burst` is the maximum number of tokens
-   * available to "glitch" rate limiter.  "glitch" is a suspicious
-   * activity from a remote endpoint.  If detected, certain amount of
-   * tokens are consumed.  If no tokens are available to consume, the
-   * connection is closed.  The rate of token generation is specified
-   * by :member:`glitch_ratelim_rate`.
+   * available to "glitch" rate limiter.  It is clamped to UINT64_MAX
+   * / NGTCP2_SECONDS.  "glitch" is a suspicious activity from a
+   * remote endpoint.  If detected, certain amount of tokens are
+   * consumed.  If no tokens are available to consume, the connection
+   * is closed.  The rate of token generation is specified by
+   * :member:`glitch_ratelim_rate`.
    *
    * .. version-added:: 1.15.0
    */
@@ -2116,6 +2118,29 @@ typedef struct ngtcp2_settings {
    * .. version-added:: 1.23.0
    */
   ngtcp2_log_write log_write;
+  /* The following fields have been added since NGTCP2_SETTINGS_V5. */
+  /**
+   * :member:`write_stream_data_thresh` is the threshold for the
+   * length of STREAM frame payload that the local endpoint sends.  If
+   * there is data more than this value to send, and the flow control
+   * does not allow sending more than this length, the data is not
+   * sent, waiting for the window update from the remote endpoint.
+   * This effectively means that flow control credit less than or
+   * equal to :member:`write_stream_data_thresh` is considered 0.  It
+   * implies that the connection to the remote endpoint that only
+   * grants less than or equal to :member:`write_stream_data_thresh`
+   * stalls.  But normally the endpoint offers far more flow control
+   * credits.  Those that only gives small credit are most likely
+   * badly configured or manipulated by an attacker, and no need to be
+   * supported.  If nonzero value is set to this field, the
+   * application should use
+   * `ngtcp2_conn_get_max_data_left_write_thresh` instead of
+   * `ngtcp2_conn_get_max_data_left`, so that the streams are not
+   * blocked by the connection-level flow control.
+   *
+   * .. version-added:: 1.26.0
+   */
+  size_t write_stream_data_thresh;
 } ngtcp2_settings;

 /**
@@ -3324,6 +3349,10 @@ typedef enum ngtcp2_connection_id_status_type {
  * :type:`ngtcp2_connection_id_status` is a callback function which is
  * called when the status of Destination Connection ID changes.
  *
+ * This callback function is not called before handshake completes.
+ * When handshake completes, it is called for Destination Connection
+ * ID in use at that time.
+ *
  * |token| is the associated stateless reset token, and it is ``NULL``
  * if no token is present.
  *
@@ -3500,6 +3529,8 @@ typedef int (*ngtcp2_stream_stop_sending)(ngtcp2_conn *conn, int64_t stream_id,
  * The callback function must return 0 if it succeeds.  Returning
  * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
  * immediately.
+ *
+ * .. version-added:: 1.24.0
  */
 typedef int (*ngtcp2_recv_stop_sending)(ngtcp2_conn *conn, int64_t stream_id,
                                         uint64_t app_error_code,
@@ -3596,6 +3627,10 @@ typedef int (*ngtcp2_get_new_connection_id2)(
  * :type:`ngtcp2_connection_id_status2` is a callback function which
  * is called when the status of Destination Connection ID changes.
  *
+ * This callback function is not called before handshake completes.
+ * When handshake completes, it is called for Destination Connection
+ * ID in use at that time.
+ *
  * |token| is the associated stateless reset token, and it is ``NULL``
  * if no token is present.
  *
@@ -3719,12 +3754,29 @@ typedef int (*ngtcp2_stream_close2)(ngtcp2_conn *conn, uint32_t flags,
                                     uint64_t tx_app_error_code, void *user_data,
                                     void *stream_user_data);

+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_extend_max_data` is a callback function which is
+ * invoked when max session data is extended.  |max_data| is a
+ * cumulative number of bytes an endpoint can send on this session.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ *
+ * .. version-added:: 1.26.0
+ */
+typedef int (*ngtcp2_extend_max_data)(ngtcp2_conn *conn, uint64_t max_data,
+                                      void *user_data);
+
 #define NGTCP2_CALLBACKS_V1 1
 #define NGTCP2_CALLBACKS_V2 2
 #define NGTCP2_CALLBACKS_V3 3
 #define NGTCP2_CALLBACKS_V4 4
 #define NGTCP2_CALLBACKS_V5 5
-#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V5
+#define NGTCP2_CALLBACKS_V6 6
+#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V6

 /**
  * @struct
@@ -4086,6 +4138,16 @@ typedef struct ngtcp2_callbacks {
    * .. version-added:: 1.25.0
    */
   ngtcp2_stream_close2 stream_close2;
+  /* The following fields have been added since
+     NGTCP2_CALLBACKS_V6. */
+  /**
+   * :member:`extend_max_data` is callback function which is invoked
+   * when the maximum offset of session data that a local endpoint can
+   * send is increased.  This callback function is optional.
+   *
+   * .. version-added:: 1.26.0
+   */
+  ngtcp2_extend_max_data extend_max_data;
 } ngtcp2_callbacks;

 /**
@@ -4532,6 +4594,8 @@ NGTCP2_EXTERN int ngtcp2_conn_install_0rtt_key(
  *
  * :macro:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     `ngtcp2_conn_install_rx_key` has already been called.
  */
 NGTCP2_EXTERN int ngtcp2_conn_install_rx_key(
   ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen,
@@ -4563,6 +4627,8 @@ NGTCP2_EXTERN int ngtcp2_conn_install_rx_key(
  *
  * :macro:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     `ngtcp2_conn_install_tx_key` has already been called.
  */
 NGTCP2_EXTERN int ngtcp2_conn_install_tx_key(
   ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen,
@@ -4578,8 +4644,12 @@ NGTCP2_EXTERN int ngtcp2_conn_install_tx_key(
  * negative error codes:
  *
  * :macro:`NGTCP2_ERR_INVALID_STATE`
- *     The previous key update has not been confirmed yet; or key
- *     update is too frequent; or new keys are not available yet.
+ *     The handshake has not been confirmed yet; or the connection is
+ *     in the closing or draining period; or the previous key update
+ *     has not been confirmed yet; or key update is too frequent; or
+ *     new keys are not available yet; or no packet encrypted with the
+ *     current key has been acknowledged since the previous key
+ *     update.
  */
 NGTCP2_EXTERN int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn,
                                                   ngtcp2_tstamp ts);
@@ -5894,8 +5964,8 @@ ngtcp2_conn_get_negotiated_version2(const ngtcp2_conn *conn);
  *
  * `ngtcp2_conn_tls_early_data_rejected` tells |conn| that early data
  * was rejected by a server during TLS handshake, or client decided
- * not to attempt early data for some reason.  |conn| discards the
- * following connection states:
+ * not to attempt early data for some reason.  Only client can call
+ * this function.  |conn| discards the following connection states:
  *
  * - Any opened streams.
  * - Stream identifier allocations.
@@ -6175,9 +6245,27 @@ NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn);
  * connection-level flow control.
  *
  * .. version-added:: 1.23.0
+ *
+ * .. seealso::
+ *   `ngtcp2_conn_get_max_data_left_write_thresh`
  */
 NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_data_left2(const ngtcp2_conn *conn);

+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_max_data_left_write_thresh` returns the number of
+ * bytes that this local endpoint can send in this connection without
+ * violating connection-level flow control.  If the remaining credit
+ * is less than or equal to
+ * :member:`ngtcp2_settings.write_stream_data_thresh`, this function
+ * returns 0.
+ *
+ * .. version-added:: 1.26.0
+ */
+NGTCP2_EXTERN uint64_t
+ngtcp2_conn_get_max_data_left_write_thresh(const ngtcp2_conn *conn);
+
 /**
  * @function
  *
@@ -6879,7 +6967,8 @@ NGTCP2_EXTERN size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn);
  * @function
  *
  * `ngtcp2_conn_get_send_quantum2` returns the maximum number of bytes
- * that can be sent in one go without packet spacing.
+ * that can be sent in one go without packet spacing.  It is at least
+ * `ngtcp2_conn_get_path_max_tx_udp_payload_size2` bytes.
  *
  * .. version-added:: 1.23.0
  */
@@ -7109,8 +7198,8 @@ NGTCP2_EXTERN void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps);
  * @function
  *
  * `ngtcp2_settings_default` initializes |settings| with the default
- * values.  First this function fills |settings| with 0, and set the
- * default value to the following fields:
+ * values.  First this function fills |settings| with 0, and sets the
+ * default values to the following fields:
  *
  * * :member:`cc_algo <ngtcp2_settings.cc_algo>` =
  *   :enum:`ngtcp2_cc_algo.NGTCP2_CC_ALGO_CUBIC`
@@ -7133,8 +7222,8 @@ NGTCP2_EXTERN void ngtcp2_settings_default_versioned(int settings_version,
  * @function
  *
  * `ngtcp2_transport_params_default` initializes |params| with the
- * default values.  First this function fills |params| with 0, and set
- * the default value to the following fields:
+ * default values.  First this function fills |params| with 0, and
+ * sets the default values to the following fields:
  *
  * * :member:`max_udp_payload_size
  *   <ngtcp2_transport_params.max_udp_payload_size>` =
diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h
index 64e8d705fa1..7e3da1994cf 100644
--- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h
+++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h
@@ -36,7 +36,7 @@
  *
  * Version number of the ngtcp2 library release.
  */
-#define NGTCP2_VERSION "1.25.0"
+#define NGTCP2_VERSION "1.26.0"

 /**
  * @macro
@@ -46,6 +46,6 @@
  * number, 8 bits for minor and 8 bits for patch. Version 1.2.3
  * becomes 0x010203.
  */
-#define NGTCP2_VERSION_NUM 0x011900
+#define NGTCP2_VERSION_NUM 0x011a00

 #endif /* !defined(NGTCP2_VERSION_H) */
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_balloc.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_balloc.c
index ea72eb83143..2cc1734fd43 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_balloc.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_balloc.c
@@ -65,8 +65,10 @@ int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n) {
   assert(n <= balloc->blklen);

   if (ngtcp2_buf_left(&balloc->buf) < n) {
+    // 0xF: the 16-byte alignment below needs up to 15 bytes when malloc
+    // returns only 4-byte aligned memory (for example on ESP-IDF heap)!
     p = ngtcp2_mem_malloc(balloc->mem,
-                          sizeof(ngtcp2_memblock_hd) + 0x8U + balloc->blklen);
+                          sizeof(ngtcp2_memblock_hd) + 0xFU + balloc->blklen);
     if (p == NULL) {
       return NGTCP2_ERR_NOMEM;
     }
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c
index 08c9876346f..ac63361236d 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c
@@ -63,6 +63,9 @@ size_t ngtcp2_callbackslen_version(int callbacks_version) {
   switch (callbacks_version) {
   case NGTCP2_CALLBACKS_VERSION:
     return sizeof(callbacks);
+  case NGTCP2_CALLBACKS_V5:
+    return offsetof(ngtcp2_callbacks, stream_close2) +
+           sizeof(callbacks.stream_close2);
   case NGTCP2_CALLBACKS_V4:
     return offsetof(ngtcp2_callbacks, recv_stop_sending) +
            sizeof(callbacks.recv_stop_sending);
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c
index 2d3c65dbf38..ca06edd2cb0 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c
@@ -40,6 +40,9 @@ uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) {
   return ngtcp2_min(10 * max_udp_payload_size, n);
 }

+/* NGTCP2_CC_SLOW_START_PACING_GAIN_H is the pacing rate for slow
+   start phase. */
+#define NGTCP2_CC_SLOW_START_PACING_GAIN_H 200
 /* 1.25 is the under-utilization avoidance factor described in
    https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */
 #define NGTCP2_CC_PACING_GAIN_H 125
@@ -47,22 +50,30 @@ uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) {
 static void init_pacing_rate(ngtcp2_conn_stat *cstat) {
   assert(cstat->cwnd);

-  cstat->pacing_interval_m = ngtcp2_max((NGTCP2_MILLISECONDS << 10) * 100 /
-                                          NGTCP2_CC_PACING_GAIN_H / cstat->cwnd,
-                                        1);
+  cstat->pacing_interval_m =
+    ngtcp2_max((NGTCP2_MILLISECONDS << 10) * 100 /
+                 NGTCP2_CC_SLOW_START_PACING_GAIN_H / cstat->cwnd,
+               1);
   cstat->send_quantum = 10 * cstat->max_tx_udp_payload_size;
 }

 static void set_pacing_rate(ngtcp2_conn_stat *cstat) {
   size_t send_quantum = 64 * 1024;
+  uint64_t pacing_gain_h;

   assert(cstat->cwnd);

+  if (cstat->cwnd < cstat->ssthresh) {
+    pacing_gain_h = NGTCP2_CC_SLOW_START_PACING_GAIN_H;
+  } else {
+    pacing_gain_h = NGTCP2_CC_PACING_GAIN_H;
+  }
+
   cstat->pacing_interval_m =
     ((cstat->first_rtt_sample_ts == UINT64_MAX ? NGTCP2_MILLISECONDS
                                                : cstat->smoothed_rtt)
      << 10) *
-    100 / NGTCP2_CC_PACING_GAIN_H / cstat->cwnd;
+    100 / pacing_gain_h / cstat->cwnd;

   cstat->pacing_interval_m = ngtcp2_max(cstat->pacing_interval_m, 1);

@@ -232,6 +243,7 @@ void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cubic, ngtcp2_log *log,
         .congestion_event = ngtcp2_cc_cubic_cc_congestion_event,
         .on_spurious_congestion = ngtcp2_cc_cubic_cc_on_spurious_congestion,
         .on_persistent_congestion = ngtcp2_cc_cubic_cc_on_persistent_congestion,
+        .on_pkt_lost_confirmed = ngtcp2_cc_cubic_cc_on_pkt_lost_confirmed,
         .reset = ngtcp2_cc_cubic_cc_reset,
       },
     .rst = rst,
@@ -375,6 +387,8 @@ void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
         cstat->ssthresh = cstat->cwnd;
         cubic->current.cwnd_prior = cstat->cwnd;
         cubic->current.w_est = cstat->cwnd;
+
+        set_pacing_rate(cstat);
       }

       return;
@@ -444,7 +458,7 @@ void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
   if (cubic->current.w_est > w_cubic) {
     cstat->cwnd = cubic->current.w_est;
   } else {
-    bytes_acked = (target - cstat->cwnd) * cstat->max_tx_udp_payload_size;
+    bytes_acked = (target - cstat->cwnd) * ack->bytes_delivered;
     m = (bytes_acked + cubic->current.pending_bytes_acked) / cstat->cwnd;

     cubic->current.pending_bytes_acked += bytes_acked;
@@ -567,6 +581,21 @@ void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc,
   set_pacing_rate(cstat);
 }

+void ngtcp2_cc_cubic_cc_on_pkt_lost_confirmed(ngtcp2_cc *cc,
+                                              ngtcp2_conn_stat *cstat,
+                                              ngtcp2_tstamp ts) {
+  ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
+  (void)cstat;
+  (void)ts;
+
+  /* The loss is real.  Discard the snapshot so that a spurious
+     congestion detected later does not restore the state from before
+     this loss. */
+  cubic_vars_reset(&cubic->undo.v);
+  cubic->undo.cwnd = 0;
+  cubic->undo.ssthresh = 0;
+}
+
 void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
                               ngtcp2_tstamp ts) {
   ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.h
index a19e90b1d0d..56c1e373fb9 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.h
@@ -170,6 +170,19 @@ typedef void (*ngtcp2_cc_on_persistent_congestion)(ngtcp2_cc *cc,
                                                    ngtcp2_conn_stat *cstat,
                                                    ngtcp2_tstamp ts);

+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_pkt_lost_confirmed` is a callback function
+ * which is called when a packet which was declared lost is removed
+ * from the retransmission buffer without being acknowledged.  The
+ * congestion event that the packet belongs to is no longer considered
+ * to be spurious.
+ */
+typedef void (*ngtcp2_cc_on_pkt_lost_confirmed)(ngtcp2_cc *cc,
+                                                ngtcp2_conn_stat *cstat,
+                                                ngtcp2_tstamp ts);
+
 /**
  * @functypedef
  *
@@ -235,6 +248,12 @@ typedef struct ngtcp2_cc {
    * is called when persistent congestion is established.
    */
   ngtcp2_cc_on_persistent_congestion on_persistent_congestion;
+  /**
+   * :member:`on_pkt_lost_confirmed` is a callback function which is
+   * called when a lost packet is removed from the retransmission
+   * buffer without being acknowledged.
+   */
+  ngtcp2_cc_on_pkt_lost_confirmed on_pkt_lost_confirmed;
   /**
    * :member:`on_ack_recv` is a callback function which is called when
    * an acknowledgement is received.
@@ -349,6 +368,10 @@ void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc,
                                                  ngtcp2_conn_stat *cstat,
                                                  ngtcp2_tstamp ts);

+void ngtcp2_cc_cubic_cc_on_pkt_lost_confirmed(ngtcp2_cc *cc,
+                                              ngtcp2_conn_stat *cstat,
+                                              ngtcp2_tstamp ts);
+
 void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
                               ngtcp2_tstamp ts);

diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c
index 4eba2aaca5d..5b9a79215fe 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c
@@ -92,6 +92,7 @@ void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid,
   dcid->bytes_sent = 0;
   dcid->bytes_recv = 0;
   dcid->max_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
+  dcid->scid_seq = 0;
 }

 void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid,
@@ -117,6 +118,7 @@ void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src) {
   dest->bytes_sent = src->bytes_sent;
   dest->bytes_recv = src->bytes_recv;
   dest->max_udp_payload_size = src->max_udp_payload_size;
+  dest->scid_seq = src->scid_seq;
 }

 void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src) {
@@ -131,20 +133,6 @@ void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src) {
   }
 }

-int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq,
-                                  const ngtcp2_cid *cid,
-                                  const ngtcp2_stateless_reset_token *token) {
-  if (dcid->seq == seq) {
-    return ngtcp2_cid_eq(&dcid->cid, cid) &&
-               (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) &&
-               ngtcp2_stateless_reset_token_eq(&dcid->token, token)
-             ? 0
-             : NGTCP2_ERR_PROTO;
-  }
-
-  return !ngtcp2_cid_eq(&dcid->cid, cid) ? 0 : NGTCP2_ERR_PROTO;
-}
-
 int ngtcp2_dcid_verify_stateless_reset_token(
   const ngtcp2_dcid *dcid, const ngtcp2_path *path,
   const ngtcp2_stateless_reset_token *token) {
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h
index f4956b0bac0..1cad017b179 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h
@@ -88,6 +88,10 @@ typedef struct ngtcp2_dcid {
   /* max_udp_payload_size is the maximum size of UDP datagram payload
      that is allowed to be sent to this path. */
   size_t max_udp_payload_size;
+  /* scid_seq is the sequence number of the Source Connection ID that
+     a remote endpoint put in the Destination Connection ID field of
+     the latest packet received on an associated path. */
+  uint64_t scid_seq;
   /* flags is bitwise OR of zero or more of NGTCP2_DCID_FLAG_*. */
   uint8_t flags;
   /* token is a stateless reset token received along with this
@@ -156,14 +160,6 @@ void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src);
  */
 void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src);

-/*
- * ngtcp2_dcid_verify_uniqueness verifies uniqueness of (|seq|, |cid|,
- * |token|) tuple against |dcid|.
- */
-int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq,
-                                  const ngtcp2_cid *cid,
-                                  const ngtcp2_stateless_reset_token *token);
-
 /*
  * ngtcp2_dcid_verify_stateless_reset_token verifies stateless reset
  * token |token| received on |path| against the one included in
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c
index 12cca44b07e..374274b567c 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c
@@ -485,11 +485,42 @@ static int conn_call_extend_max_stream_data(ngtcp2_conn *conn,
   return 0;
 }

+static int conn_call_extend_max_data(ngtcp2_conn *conn, uint64_t datalen) {
+  int rv;
+
+  if (!conn->callbacks.extend_max_data) {
+    return 0;
+  }
+
+  rv = conn->callbacks.extend_max_data(conn, datalen, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
 static int conn_call_dcid_status(ngtcp2_conn *conn,
                                  ngtcp2_connection_id_status_type type,
                                  const ngtcp2_dcid *dcid) {
   int rv;

+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+  case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE:
+  case NGTCP2_CS_SERVER_INITIAL:
+  case NGTCP2_CS_SERVER_WAIT_HANDSHAKE:
+    /* The current Destination Connection ID is activated when
+       handshake completes.  Until then, ngtcp2_conn_get_active_dcid3
+       returns nothing, and an application which closes the connection
+       has no means to forget Destination Connection ID which
+       NEW_CONNECTION_ID frame in 0-RTT or 0.5-RTT packet made a local
+       endpoint use. */
+    return 0;
+  default:
+    break;
+  }
+
   if (conn->callbacks.dcid_status2) {
     rv = conn->callbacks.dcid_status2(
       conn, type, dcid->seq, &dcid->cid,
@@ -1331,6 +1362,11 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
     buflen += NGTCP2_LOG_BUFLEN;
   }

+  // buflen_align() aligns lengths, buf_align() addresses: when malloc
+  // returns only 4-byte aligned memory (ESP-IDF heap), the sections
+  // start up to 7 bytes later than computed.
+  buflen += 0x7U;
+
   buf = ngtcp2_mem_calloc(mem, 1, buflen);
   if (buf == NULL) {
     return NGTCP2_ERR_NOMEM;
@@ -1362,9 +1398,9 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,

   ngtcp2_pq_init(&(*pconn)->tx.strmq, cycle_less, mem);

-  ngtcp2_idtr_init(&(*pconn)->remote.bidi.idtr, mem);
+  ngtcp2_idtr_init(&(*pconn)->bidi.idtr, mem);

-  ngtcp2_idtr_init(&(*pconn)->remote.uni.idtr, mem);
+  ngtcp2_idtr_init(&(*pconn)->uni.idtr, mem);

   ngtcp2_static_ringbuf_path_challenge_init(&(*pconn)->rx.path_challenge);

@@ -1391,13 +1427,18 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
   (*pconn)->local.settings = *settings;

   if (settings->tokenlen) {
-    tokenbuf = ngtcp2_mem_malloc(mem, settings->tokenlen);
-    if (tokenbuf == NULL) {
-      rv = NGTCP2_ERR_NOMEM;
-      goto fail_token;
+    if (settings->tokenlen > NGTCP2_MAX_TOKENLEN) {
+      (*pconn)->local.settings.token = NULL;
+      (*pconn)->local.settings.tokenlen = 0;
+    } else {
+      tokenbuf = ngtcp2_mem_malloc(mem, settings->tokenlen);
+      if (tokenbuf == NULL) {
+        rv = NGTCP2_ERR_NOMEM;
+        goto fail_token;
+      }
+      memcpy(tokenbuf, settings->token, settings->tokenlen);
+      (*pconn)->local.settings.token = tokenbuf;
     }
-    memcpy(tokenbuf, settings->token, settings->tokenlen);
-    (*pconn)->local.settings.token = tokenbuf;
   } else {
     (*pconn)->local.settings.token = NULL;
   }
@@ -1596,7 +1637,6 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
   (*pconn)->tx.last_max_data_ts = UINT64_MAX;
   (*pconn)->tx.pacing.next_ts = UINT64_MAX;
   (*pconn)->tx.last_blocked_offset = UINT64_MAX;
-  (*pconn)->rx.preferred_addr.pkt_num = -1;
   (*pconn)->early.discard_started_ts = UINT64_MAX;

   conn_reset_ecn_validation_state(*pconn);
@@ -1646,8 +1686,8 @@ int ngtcp2_conn_client_new_versioned(
   }
   (*pconn)->rcid = *dcid;
   (*pconn)->state = NGTCP2_CS_CLIENT_INITIAL;
-  (*pconn)->local.bidi.next_stream_id = 0;
-  (*pconn)->local.uni.next_stream_id = 2;
+  (*pconn)->tx.bidi.next_stream_id = 0;
+  (*pconn)->tx.uni.next_stream_id = 2;
   (*pconn)->flags |= NGTCP2_CONN_FLAG_CRUMBLE_INITIAL_CRYPTO;

   rv = ngtcp2_conn_commit_local_transport_params(*pconn);
@@ -1678,8 +1718,8 @@ int ngtcp2_conn_server_new_versioned(
   }

   (*pconn)->state = NGTCP2_CS_SERVER_INITIAL;
-  (*pconn)->local.bidi.next_stream_id = 1;
-  (*pconn)->local.uni.next_stream_id = 3;
+  (*pconn)->tx.bidi.next_stream_id = 1;
+  (*pconn)->tx.uni.next_stream_id = 3;

   if ((*pconn)->local.settings.tokenlen) {
     /* Usage of token lifts amplification limit */
@@ -1826,8 +1866,8 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) {

   ngtcp2_mem_free(conn->mem, (uint8_t *)conn->rx.ccerr.reason);

-  ngtcp2_idtr_free(&conn->remote.uni.idtr);
-  ngtcp2_idtr_free(&conn->remote.bidi.idtr);
+  ngtcp2_idtr_free(&conn->uni.idtr);
+  ngtcp2_idtr_free(&conn->bidi.idtr);
   ngtcp2_pq_free(&conn->tx.strmq);
   ngtcp2_map_each(&conn->strms, delete_strms_each, (void *)conn);
   ngtcp2_map_free(&conn->strms);
@@ -1990,8 +2030,11 @@ static int conn_cwnd_is_zero(const ngtcp2_conn *conn) {

 /*
  * conn_retry_early_payloadlen returns the estimated wire length of
- * the first STREAM frame of 0-RTT packet which should be
- * retransmitted due to Retry packet.
+ * the first frame of 0-RTT packet which is generated from
+ * conn->tx.strmq.  It is either STREAM frame which should be
+ * retransmitted due to Retry packet, or the other stream related
+ * frame (e.g., RESET_STREAM, STOP_SENDING, STREAM_DATA_BLOCKED, and
+ * MAX_STREAM_DATA).
  */
 static uint64_t conn_retry_early_payloadlen(ngtcp2_conn *conn) {
   ngtcp2_frame_chain *frc;
@@ -2002,32 +2045,35 @@ static uint64_t conn_retry_early_payloadlen(ngtcp2_conn *conn) {
     return 0;
   }

-  for (; !ngtcp2_pq_empty(&conn->tx.strmq);) {
-    strm = ngtcp2_conn_tx_strmq_top(conn);
-    if (ngtcp2_strm_streamfrq_empty(strm)) {
-      ngtcp2_conn_tx_strmq_pop(conn);
-      continue;
-    }
+  if (ngtcp2_pq_empty(&conn->tx.strmq)) {
+    return 0;
+  }

-    frc = ngtcp2_strm_streamfrq_top(strm);
+  strm = ngtcp2_conn_tx_strmq_top(conn);
+  if (ngtcp2_strm_streamfrq_empty(strm)) {
+    /* strm has no STREAM frame to send, but is queued to send the
+       other stream related frames (e.g., RESET_STREAM, STOP_SENDING,
+       or MAX_STREAM_DATA).  Do not remove it from the queue here,
+       otherwise those frames are never sent.  conn_write_pkt writes
+       them, and removes strm from the queue. */
+    return NGTCP2_STREAM_OVERHEAD;
+  }

-    len = ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt) +
-          NGTCP2_STREAM_OVERHEAD;
+  frc = ngtcp2_strm_streamfrq_top(strm);

-    /* Take the min because in conn_should_pad_pkt we take max in
-       order to deal with unbreakable DATAGRAM. */
-    return ngtcp2_min(len, NGTCP2_MIN_COALESCED_PAYLOADLEN);
-  }
+  len = ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt) +
+        NGTCP2_STREAM_OVERHEAD;

-  return 0;
+  /* Take the min because in conn_should_pad_pkt we take max in order
+     to deal with unbreakable DATAGRAM. */
+  return ngtcp2_min(len, NGTCP2_MIN_COALESCED_PAYLOADLEN);
 }

 /*
  * conn_verify_dcid verifies that destination connection ID in |hd| is
- * valid for the connection.  If it is successfully verified and the
- * remote endpoint uses new DCID in the packet, nonzero value is
- * assigned to |*pnew_cid_used| if it is not NULL.  Otherwise 0 is
- * assigned to it.
+ * valid for the connection.  If it is successfully verified, and
+ * |pseq| is not NULL, the sequence number of the connection ID is
+ * assigned to |*pseq|.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
@@ -2037,7 +2083,7 @@ static uint64_t conn_retry_early_payloadlen(ngtcp2_conn *conn) {
  * NGTCP2_ERR_INVALID_ARGUMENT
  *     |dcid| is not known to the local endpoint.
  */
-static int conn_verify_dcid(ngtcp2_conn *conn, int *pnew_cid_used,
+static int conn_verify_dcid(ngtcp2_conn *conn, uint64_t *pseq,
                             const ngtcp2_pkt_hd *hd) {
   ngtcp2_ksl_it it;
   ngtcp2_scid *scid;
@@ -2062,12 +2108,10 @@ static int conn_verify_dcid(ngtcp2_conn *conn, int *pnew_cid_used,
         return rv;
       }
     }
+  }

-    if (pnew_cid_used) {
-      *pnew_cid_used = 1;
-    }
-  } else if (pnew_cid_used) {
-    *pnew_cid_used = 0;
+  if (pseq) {
+    *pseq = scid->seq;
   }

   return 0;
@@ -2728,18 +2772,9 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest,
       }
       /* We had pktns->rtb.num_retransmittable > 0 but the contents of
          those packets have been acknowledged (i.e., retransmission in
-         another packet).  For server, in this case, we don't have to
-         send any probe packet.  Client needs to send probe packets
-         until it knows that server has completed address
-         validation. */
-      if (pktns->rtb.num_pto_eliciting == 0 &&
-          (conn->server ||
-           (conn->flags & NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED))) {
-        pktns->rtb.probe_pkt_left = 0;
-        ngtcp2_conn_set_loss_detection_timer(conn, ts);
-        /* TODO If packet is empty, we should return now if cwnd is
-           zero. */
-      }
+         another packet).  Those packets are still in flight, and we
+         have to send PING as a probe packet to know whether they are
+         lost or not. */
     }

     if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
@@ -2804,6 +2839,13 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest,
         (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
       padded = 1;
     }
+
+    if (!hd_logged) {
+      hd_logged = 1;
+      ngtcp2_log_tx_pkt_hd(&conn->log, &hd);
+      ngtcp2_qlog_pkt_sent_start(&conn->qlog);
+    }
+
     ngtcp2_log_tx_fr(&conn->log, &hd, &lfr);
     ngtcp2_qlog_write_frame(&conn->qlog, &lfr);
   }
@@ -3071,9 +3113,13 @@ static ngtcp2_ssize conn_write_client_initial(ngtcp2_conn *conn,
                                               ngtcp2_tstamp ts) {
   int rv;

-  rv = conn_call_client_initial(conn);
-  if (rv != 0) {
-    return rv;
+  if (!(conn->flags & NGTCP2_CONN_FLAG_CLIENT_INITIAL_CALLED)) {
+    rv = conn_call_client_initial(conn);
+    if (rv != 0) {
+      return rv;
+    }
+
+    conn->flags |= NGTCP2_CONN_FLAG_CLIENT_INITIAL_CALLED;
   }

   return conn_write_handshake_pkt(
@@ -3082,16 +3128,17 @@ static ngtcp2_ssize conn_write_client_initial(ngtcp2_conn *conn,
 }

 /*
- * dcid_tx_left returns the maximum number of bytes that server is
- * allowed to send to an unvalidated path associated to |dcid|.
+ * dcid_tx_left returns the maximum number of bytes that an endpoint
+ * is allowed to send to an unvalidated path associated to |dcid|.
  */
 static uint64_t dcid_tx_left(const ngtcp2_dcid *dcid) {
   if (dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) {
     return SIZE_MAX;
   }
-  /* From QUIC spec: Prior to validating the client address, servers
-     MUST NOT send more than three times as many bytes as the number
-     of bytes they have received. */
+  /* From QUIC spec: After receiving packets from an address that is
+     not yet validated, an endpoint MUST limit the amount of data it
+     sends to the unvalidated address to three times the amount of
+     data received from that address. */
   assert(dcid->bytes_recv * 3 >= dcid->bytes_sent);

   return dcid->bytes_recv * 3 - dcid->bytes_sent;
@@ -3329,6 +3376,16 @@ static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) {
       return rv;
     }

+    /* Allocate scid before calling get_new_connection_id callback.
+       An application associates a new Connection ID with conn inside
+       the callback, and relies on conn->scid.set to remove the
+       association later. */
+    scid = ngtcp2_mem_malloc(conn->mem, sizeof(*scid));
+    if (scid == NULL) {
+      rv = NGTCP2_ERR_NOMEM;
+      goto fail;
+    }
+
     nfrc->fr.new_connection_id.type = NGTCP2_FRAME_NEW_CONNECTION_ID;
     nfrc->fr.new_connection_id.seq = ++conn->scid.last_seq;
     nfrc->fr.new_connection_id.retire_prior_to = 0;
@@ -3355,18 +3412,14 @@ static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) {
       goto fail;
     }

-    scid = ngtcp2_mem_malloc(conn->mem, sizeof(*scid));
-    if (scid == NULL) {
-      rv = NGTCP2_ERR_NOMEM;
-      goto fail;
-    }
-
     ngtcp2_scid_init(scid, nfrc->fr.new_connection_id.seq,
                      &nfrc->fr.new_connection_id.cid);

     rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scid->cid, scid);
     if (rv != 0) {
-      ngtcp2_mem_free(conn->mem, scid);
+      /* Let an application remove the association it has just
+         made. */
+      conn_call_remove_connection_id(conn, &scid->cid);
       goto fail;
     }

@@ -3381,6 +3434,7 @@ static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) {
   return 0;

 fail:
+  ngtcp2_mem_free(conn->mem, scid);
   ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);

   return rv;
@@ -3590,7 +3644,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt);
       ndatalen = conn_enforce_flow_control(conn, vmsg->stream.strm, datalen);
       /* 0 length STREAM frame is allowed */
-      if (ndatalen || datalen == 0) {
+      if (datalen == 0 ||
+          (ndatalen &&
+           (datalen <= conn->local.settings.write_stream_data_thresh ||
+            ndatalen > conn->local.settings.write_stream_data_thresh))) {
         send_stream = 1;
       } else {
         stream_blocked = 1;
@@ -3729,7 +3786,8 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
           ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb);

           pkt_empty = 0;
-          rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING;
+          rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING |
+                             NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING;
           require_padding = require_padding || !conn->server ||
                             destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE;
           /* We don't retransmit PATH_RESPONSE. */
@@ -3801,8 +3859,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       case NGTCP2_FRAME_STREAM:
         ngtcp2_unreachable();
       case NGTCP2_FRAME_MAX_STREAMS_BIDI:
-        if ((*pfrc)->fr.max_streams.max_streams <
-            conn->remote.bidi.max_streams) {
+        if ((*pfrc)->fr.max_streams.max_streams < conn->rx.bidi.max_streams) {
           frc = *pfrc;
           *pfrc = (*pfrc)->next;
           ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
@@ -3810,8 +3867,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
         }
         break;
       case NGTCP2_FRAME_MAX_STREAMS_UNI:
-        if ((*pfrc)->fr.max_streams.max_streams <
-            conn->remote.uni.max_streams) {
+        if ((*pfrc)->fr.max_streams.max_streams < conn->rx.uni.max_streams) {
           frc = *pfrc;
           *pfrc = (*pfrc)->next;
           ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
@@ -3849,7 +3905,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
         }
         break;
       case NGTCP2_FRAME_DATA_BLOCKED:
-        if ((*pfrc)->fr.data_blocked.offset != conn->tx.max_offset) {
+        if ((*pfrc)->fr.data_blocked.max_data != conn->tx.max_offset) {
           frc = *pfrc;
           *pfrc = (*pfrc)->next;
           ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
@@ -3993,38 +4049,82 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
                                NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
                                NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
             pfrc = &(*pfrc)->next;
+
+            /* An application might have shut down or extended the
+               flow control window of any stream inside the callback
+               function.  strm might not be the top of conn->tx.strmq
+               anymore, and its flags might have been changed.  Start
+               over from the top of the queue. */
+            if (ngtcp2_conn_tx_strmq_top(conn) != strm ||
+                (strm->flags & NGTCP2_STRM_FLAG_SEND_RESET_STREAM)) {
+              continue;
+            }
           }
         }

-        if (!(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) &&
-            strm_should_send_stream_data_blocked(strm)) {
-          rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
-          if (rv != 0) {
-            return rv;
-          }
+        if (strm->flags & NGTCP2_STRM_FLAG_SEND_STREAM_DATA_BLOCKED) {
+          if (!(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR)) {
+            if (conn_should_send_data_blocked(conn)) {
+              rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+              if (rv != 0) {
+                return rv;
+              }
+
+              nfrc->fr.data_blocked = (ngtcp2_data_blocked){
+                .type = NGTCP2_FRAME_DATA_BLOCKED,
+                .max_data = conn->tx.max_offset,
+              };
+              *pfrc = nfrc;

-          nfrc->fr.stream_data_blocked = (ngtcp2_stream_data_blocked){
-            .type = NGTCP2_FRAME_STREAM_DATA_BLOCKED,
-            .stream_id = strm->stream_id,
-            .offset = strm->tx.max_offset,
-          };
-          *pfrc = nfrc;
+              conn->tx.last_blocked_offset = conn->tx.max_offset;

-          strm->tx.last_blocked_offset = strm->tx.max_offset;
+              rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd,
+                                               &nfrc->fr);
+              if (rv != 0) {
+                assert(NGTCP2_ERR_NOBUF == rv);

-          rv =
-            conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
-          if (rv != 0) {
-            assert(NGTCP2_ERR_NOBUF == rv);
+                break;
+              }

-            break;
+              pkt_empty = 0;
+              rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING |
+                                 NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
+                                 NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
+              pfrc = &(*pfrc)->next;
+            }
+
+            if (strm_should_send_stream_data_blocked(strm)) {
+              rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+              if (rv != 0) {
+                return rv;
+              }
+
+              nfrc->fr.stream_data_blocked = (ngtcp2_stream_data_blocked){
+                .type = NGTCP2_FRAME_STREAM_DATA_BLOCKED,
+                .stream_id = strm->stream_id,
+                .max_stream_data = strm->tx.max_offset,
+              };
+              *pfrc = nfrc;
+
+              strm->tx.last_blocked_offset = strm->tx.max_offset;
+
+              rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd,
+                                               &nfrc->fr);
+              if (rv != 0) {
+                assert(NGTCP2_ERR_NOBUF == rv);
+
+                break;
+              }
+
+              pkt_empty = 0;
+              rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING |
+                                 NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
+                                 NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
+              pfrc = &(*pfrc)->next;
+            }
           }

-          pkt_empty = 0;
-          rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING |
-                             NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
-                             NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE;
-          pfrc = &(*pfrc)->next;
+          strm->flags &= ~NGTCP2_STRM_FLAG_SEND_STREAM_DATA_BLOCKED;
         }

         if (!(strm->flags &
@@ -4148,9 +4248,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
     /* Write MAX_STREAMS after RESET_STREAM so that we can extend
        stream ID space in one packet. */
     if (*pfrc == NULL &&
-        conn->remote.bidi.unsent_max_streams > conn->remote.bidi.max_streams) {
+        conn->rx.bidi.unsent_max_streams > conn->rx.bidi.max_streams) {
       rv = conn_call_extend_max_remote_streams_bidi(
-        conn, conn->remote.bidi.unsent_max_streams);
+        conn, conn->rx.bidi.unsent_max_streams);
       if (rv != 0) {
         assert(ngtcp2_err_is_fatal(rv));
         return rv;
@@ -4163,11 +4263,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       }
       nfrc->fr.max_streams = (ngtcp2_max_streams){
         .type = NGTCP2_FRAME_MAX_STREAMS_BIDI,
-        .max_streams = conn->remote.bidi.unsent_max_streams,
+        .max_streams = conn->rx.bidi.unsent_max_streams,
       };
       *pfrc = nfrc;

-      conn->remote.bidi.max_streams = conn->remote.bidi.unsent_max_streams;
+      conn->rx.bidi.max_streams = conn->rx.bidi.unsent_max_streams;

       rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr);
       if (rv != 0) {
@@ -4182,9 +4282,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
     }

     if (*pfrc == NULL &&
-        conn->remote.uni.unsent_max_streams > conn->remote.uni.max_streams) {
+        conn->rx.uni.unsent_max_streams > conn->rx.uni.max_streams) {
       rv = conn_call_extend_max_remote_streams_uni(
-        conn, conn->remote.uni.unsent_max_streams);
+        conn, conn->rx.uni.unsent_max_streams);
       if (rv != 0) {
         assert(ngtcp2_err_is_fatal(rv));
         return rv;
@@ -4197,11 +4297,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       }
       nfrc->fr.max_streams = (ngtcp2_max_streams){
         .type = NGTCP2_FRAME_MAX_STREAMS_UNI,
-        .max_streams = conn->remote.uni.unsent_max_streams,
+        .max_streams = conn->rx.uni.unsent_max_streams,
       };
       *pfrc = nfrc;

-      conn->remote.uni.max_streams = conn->remote.uni.unsent_max_streams;
+      conn->rx.uni.max_streams = conn->rx.uni.unsent_max_streams;

       rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr);
       if (rv != 0) {
@@ -4227,16 +4327,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       }

       /* We had pktns->rtb.num_retransmittable > 0 but we were unable
-         to reclaim any frame.  In this case, we do not have to send
-         any probe packet. */
-      if (pktns->rtb.num_pto_eliciting == 0) {
-        pktns->rtb.probe_pkt_left = 0;
-        ngtcp2_conn_set_loss_detection_timer(conn, ts);
-
-        if (pkt_empty && conn_cwnd_is_zero(conn) && !require_padding) {
-          return 0;
-        }
-      }
+         to reclaim any frame.  Those packets are still in flight, and
+         we have to send PING as a probe packet to know whether they
+         are lost or not. */
     }
   } else {
     pfrc = conn->pkt.pfrc;
@@ -4248,17 +4341,19 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

   left = ngtcp2_ppe_left(ppe);

+  /* An application might have shut down the stream inside
+     stream_stop_sending callback function called above. */
   if (*pfrc == NULL && send_stream && ngtcp2_pq_empty(&conn->tx.strmq) &&
+      !(vmsg->stream.strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) &&
       (wdatalen = ngtcp2_pkt_stream_max_datalen(
          vmsg->stream.strm->stream_id, vmsg->stream.strm->tx.offset, ndatalen,
          left)) != (size_t)-1 &&
       (wdatalen == ndatalen || wdatalen >= NGTCP2_MIN_STREAM_DATALEN) &&
       (wdatalen || datalen == 0)) {
-    ndatalen = wdatalen;
     datacnt = ngtcp2_vec_copy_at_most(data, NGTCP2_MAX_STREAM_DATACNT,
                                       vmsg->stream.data, vmsg->stream.datacnt,
-                                      (size_t)ndatalen);
-    ndatalen = ngtcp2_vec_len(data, datacnt);
+                                      (size_t)wdatalen);
+    wdatalen = ngtcp2_vec_len(data, datacnt);

     assert((datacnt == 0 && datalen == 0) || (datacnt && datalen));

@@ -4271,15 +4366,13 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

     nfrc->fr.stream.type = NGTCP2_FRAME_STREAM;
     nfrc->fr.stream.flags = 0;
-    nfrc->fr.stream.fin = 0;
+    nfrc->fr.stream.fin = (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
+                          wdatalen == datalen;
     nfrc->fr.stream.stream_id = vmsg->stream.strm->stream_id;
     nfrc->fr.stream.offset = vmsg->stream.strm->tx.offset;
     nfrc->fr.stream.datacnt = datacnt;
     ngtcp2_vec_copy(nfrc->fr.stream.data, data, datacnt);

-    nfrc->fr.stream.fin = (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
-                          ndatalen == datalen;
-
     rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
     if (rv != 0) {
       ngtcp2_unreachable();
@@ -4295,8 +4388,8 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

     ++conn->frame_counts.stream;

-    vmsg->stream.strm->tx.offset += ndatalen;
-    conn->tx.offset += ndatalen;
+    vmsg->stream.strm->tx.offset += wdatalen;
+    conn->tx.offset += wdatalen;
     vmsg->stream.strm->flags |= NGTCP2_STRM_FLAG_ANY_SENT;

     if (nfrc->fr.stream.fin) {
@@ -4304,7 +4397,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
     }

     if (vmsg->stream.pdatalen) {
-      *vmsg->stream.pdatalen = (ngtcp2_ssize)ndatalen;
+      *vmsg->stream.pdatalen = (ngtcp2_ssize)wdatalen;
     }
   } else {
     send_stream = 0;
@@ -4312,8 +4405,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

   if (vmsg && vmsg->type == NGTCP2_VMSG_TYPE_STREAM &&
       ((stream_blocked && *pfrc == NULL) ||
-       (send_stream &&
-        !(vmsg->stream.strm->flags & NGTCP2_STRM_FLAG_SHUT_WR)))) {
+       (send_stream && !(vmsg->stream.strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) &&
+        ndatalen < datalen && ndatalen == wdatalen))) {
+    strm = vmsg->stream.strm;
+
     if (conn_should_send_data_blocked(conn)) {
       rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
       if (rv != 0) {
@@ -4324,7 +4419,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

       nfrc->fr.data_blocked = (ngtcp2_data_blocked){
         .type = NGTCP2_FRAME_DATA_BLOCKED,
-        .offset = conn->tx.offset,
+        .max_data = conn->tx.offset,
       };

       rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
@@ -4333,6 +4428,13 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

         /* We cannot add nfrc to pktns->tx.frq here. */
         ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+
+        strm->flags |= NGTCP2_STRM_FLAG_SEND_STREAM_DATA_BLOCKED;
+
+        rv = ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
+        if (rv != 0) {
+          return rv;
+        }
       } else {
         *pfrc = nfrc;
         pfrc = &(*pfrc)->next;
@@ -4346,8 +4448,6 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       }
     }

-    strm = vmsg->stream.strm;
-
     if (strm_should_send_stream_data_blocked(strm)) {
       rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
       if (rv != 0) {
@@ -4359,7 +4459,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       nfrc->fr.stream_data_blocked = (ngtcp2_stream_data_blocked){
         .type = NGTCP2_FRAME_STREAM_DATA_BLOCKED,
         .stream_id = strm->stream_id,
-        .offset = strm->tx.max_offset,
+        .max_stream_data = strm->tx.max_offset,
       };

       rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
@@ -4369,12 +4469,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
         /* We cannot add nfrc to pktns->tx.frq here. */
         ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);

-        if (!ngtcp2_strm_is_tx_queued(strm)) {
-          strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
-          rv = ngtcp2_conn_tx_strmq_push(conn, strm);
-          if (rv != 0) {
-            return rv;
-          }
+        strm->flags |= NGTCP2_STRM_FLAG_SEND_STREAM_DATA_BLOCKED;
+
+        rv = ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
+        if (rv != 0) {
+          return rv;
         }
       } else {
         *pfrc = nfrc;
@@ -4448,6 +4547,13 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
         conn->pkt.hd_logged = hd_logged;
         conn->pkt.require_padding = require_padding;
         conn->flags |= NGTCP2_CONN_FLAG_PPE_PENDING;
+      } else {
+        /* The packet might have been left open by the previous call
+           with NGTCP2_WRITE_PKT_FLAG_MORE.  It is still empty, and
+           nothing is going to be added to it, so discard it.
+           Otherwise, NGTCP2_CONN_FLAG_PPE_PENDING is left set after
+           returning to the application. */
+        conn_reset_ppe_pending(conn);
       }

       return NGTCP2_ERR_STREAM_DATA_BLOCKED;
@@ -4485,6 +4591,13 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
           stream_blocked) {
         return NGTCP2_ERR_STREAM_DATA_BLOCKED;
       }
+
+      /* In case when stream is shutted down inside stop_sending
+         callback */
+      if (vmsg->stream.strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) {
+        return NGTCP2_ERR_STREAM_SHUT_WR;
+      }
+
       break;
     case NGTCP2_VMSG_TYPE_DATAGRAM:
       if (send_datagram && ngtcp2_ppe_left(ppe)) {
@@ -4508,8 +4621,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr);
       if (rv != 0) {
         assert(rv == NGTCP2_ERR_NOBUF);
-        /* TODO If buffer is too small, PING cannot be written if
-           packet is still empty. */
+        /* ngtcp2_ppe_encode_hd ensures that an empty packet has the
+           space for PING.  If we get here, the packet is not
+           empty. */
+        assert(!pkt_empty);
       } else {
         rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING;
         if (conn->pktns.rtb.probe_pkt_left) {
@@ -4544,6 +4659,13 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
       padded = 1;
     }
     lfr.padding.type = NGTCP2_FRAME_PADDING;
+
+    if (!hd_logged) {
+      hd_logged = 1;
+      ngtcp2_log_tx_pkt_hd(&conn->log, hd);
+      ngtcp2_qlog_pkt_sent_start(&conn->qlog);
+    }
+
     ngtcp2_log_tx_fr(&conn->log, hd, &lfr);
     ngtcp2_qlog_write_frame(&conn->qlog, &lfr);
   }
@@ -4915,6 +5037,8 @@ static int dcidtr_on_retire(const ngtcp2_dcid *dcid, void *user_data) {
  *
  * NGTCP2_ERR_NOMEM
  *     Out of memory
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  */
 static int conn_retire_active_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid,
                                    ngtcp2_tstamp ts) {
@@ -4942,6 +5066,8 @@ static int conn_retire_active_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid,
  *
  * NGTCP2_ERR_CONN_ID_BLOCKED
  *     No unused DCID is available
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  * NGTCP2_ERR_NOMEM
  *     Out of memory
  */
@@ -5068,6 +5194,8 @@ static ngtcp2_ssize conn_write_pmtud_probe(ngtcp2_conn *conn,
  *
  * NGTCP2_ERR_NOMEM
  *     Out of memory
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  */
 static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   int rv = 0;
@@ -5077,7 +5205,10 @@ static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
     return 0;
   }

-  if (pv->dcid.cid.datalen && pv->dcid.seq != conn->dcid.current.seq) {
+  if (pv->dcid.cid.datalen && pv->dcid.seq != conn->dcid.current.seq &&
+      (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) ||
+       !(pv->flags & NGTCP2_PV_FLAG_DONT_RETIRE_FALLBACK) ||
+       pv->dcid.seq != pv->fallback_dcid.seq)) {
     rv = conn_retire_active_dcid(conn, &pv->dcid, ts);
     if (rv != 0) {
       goto fin;
@@ -5085,6 +5216,7 @@ static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   }

   if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+      !(pv->flags & NGTCP2_PV_FLAG_DONT_RETIRE_FALLBACK) &&
       pv->fallback_dcid.cid.datalen &&
       pv->fallback_dcid.seq != conn->dcid.current.seq &&
       pv->fallback_dcid.seq != pv->dcid.seq) {
@@ -5113,6 +5245,8 @@ fin:
  *     Out of memory
  * NGTCP2_ERR_CALLBACK_FAILURE
  *     User-defined callback function failed.
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  */
 static int conn_abort_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   ngtcp2_pv *pv = conn->pv;
@@ -5286,7 +5420,11 @@ static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn,

   if (ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) {
     conn->dcid.current.bytes_sent += (uint64_t)nwrite;
-  } else {
+  } else if (conn->server) {
+    /* Client starts path validation before it receives anything from
+       the path.  Its probes are not charged to the anti-amplification
+       limit, which only restricts what client sends in response to
+       the packets received from that path. */
     pv->dcid.bytes_sent += (uint64_t)nwrite;
   }

@@ -5304,6 +5442,8 @@ static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn,
  *     Out of memory
  * NGTCP2_ERR_CALLBACK_FAILURE
  *     User-defined callback function failed.
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  */
 static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn,
                                              ngtcp2_path *path,
@@ -5314,7 +5454,6 @@ static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn,
   ngtcp2_dcid *dcid = NULL;
   ngtcp2_frame lfr;
   ngtcp2_ssize nwrite;
-  int rv;
   uint64_t tx_left;

   for (; ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb);) {
@@ -5338,11 +5477,23 @@ static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn,
     }

     if (conn->server) {
-      break;
+      /* conn_recv_pkt_from_new_path binds DCID to the path, and
+         credits the received bytes to it when PATH_CHALLENGE is
+         received.  Do not bind new DCID here because it has no
+         credit, and nothing can be sent with it. */
+      dcid = ngtcp2_dcidtr_find_bound_dcid(&conn->dcid.dtr, &pcent->ps.path);
+      if (dcid && dcid_tx_left(dcid)) {
+        break;
+      }
     }

     /* Client does not expect to respond to path validation against
-       unknown path */
+       unknown path.  Server has no DCID bound to the path, or is
+       blocked by the anti-amplification limit.  Waiting does not help
+       because they are only resolved by receiving another packet from
+       the path, which carries new PATH_CHALLENGE if a remote endpoint
+       still needs the path.  Drop this entry so that it does not
+       block the others. */
     ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb);
     pcent = NULL;
   }
@@ -5351,24 +5502,14 @@ static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn,
     return 0;
   }

-  if (dcid == NULL) {
-    /* client is expected to have |path| in conn->dcid.current or
-       conn->pv. */
-    assert(conn->server);
-
-    rv = conn_bind_dcid(conn, &dcid, &pcent->ps.path, ts);
-    if (rv != 0) {
-      if (ngtcp2_err_is_fatal(rv)) {
-        return rv;
-      }
-      return 0;
-    }
-  }
-
   destlen = ngtcp2_min(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE);

-  if (conn->server && !(dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
-    tx_left = conn_server_tx_left(conn, dcid);
+  /* |dcid| is not bound to the current path here.  Client is also
+     subject to the anti-amplification limit because the path it is
+     validating might lead to an address chosen by server (e.g.,
+     preferred address). */
+  if (!(dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+    tx_left = dcid_tx_left(dcid);
     destlen = (size_t)ngtcp2_min((uint64_t)destlen, tx_left);
     if (destlen == 0) {
       return 0;
@@ -5625,7 +5766,8 @@ int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
  * NGTCP2_ERR_NOMEM
  *     Out of memory
  * NGTCP2_ERR_PROTO
- *     |fr| acknowledges a packet this endpoint has not sent.
+ *     |fr| acknowledges a packet this endpoint has not sent, or a
+ *     packet which was sent after |pkt_ts|.
  * NGTCP2_ERR_CALLBACK_FAILURE
  *     User callback failed.
  */
@@ -5706,21 +5848,21 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn,

   if (bidi) {
     if (local_stream) {
-      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+      if (conn->tx.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_streams <
+    } else if (conn->rx.bidi.max_streams <
                ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.bidi.idtr;
+    idtr = &conn->bidi.idtr;
   } else {
-    if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) {
+    if (!local_stream || conn->tx.uni.next_stream_id <= fr->stream_id) {
       return NGTCP2_ERR_STREAM_STATE;
     }

-    idtr = &conn->remote.uni.idtr;
+    idtr = &conn->uni.idtr;
   }

   strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
@@ -5765,10 +5907,6 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn,
   }

   if (strm->tx.max_offset >= fr->max_stream_data) {
-    if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-      return NGTCP2_ERR_INTERNAL;
-    }
-
     return 0;
   }

@@ -5776,27 +5914,34 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn,

   /* Don't call callback if stream is half-closed local */
   if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) {
-    if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-      return NGTCP2_ERR_INTERNAL;
-    }
-
     return 0;
   }

-  rv = conn_call_extend_max_stream_data(conn, strm, fr->stream_id,
-                                        fr->max_stream_data);
-  if (rv != 0) {
-    return rv;
+  if (strm->tx.max_offset <=
+      strm->tx.offset + conn->local.settings.write_stream_data_thresh) {
+    return 0;
   }

-  return 0;
+  return conn_call_extend_max_stream_data(conn, strm, fr->stream_id,
+                                          fr->max_stream_data);
 }

 /*
  * conn_recv_max_data processes received MAX_DATA frame |fr|.
  */
-static void conn_recv_max_data(ngtcp2_conn *conn, const ngtcp2_max_data *fr) {
-  conn->tx.max_offset = ngtcp2_max(conn->tx.max_offset, fr->max_data);
+static int conn_recv_max_data(ngtcp2_conn *conn, const ngtcp2_max_data *fr) {
+  if (fr->max_data <= conn->tx.max_offset) {
+    return 0;
+  }
+
+  conn->tx.max_offset = fr->max_data;
+
+  if (conn->tx.max_offset <=
+      conn->tx.offset + conn->local.settings.write_stream_data_thresh) {
+    return 0;
+  }
+
+  return conn_call_extend_max_data(conn, fr->max_data);
 }

 /*
@@ -6028,7 +6173,7 @@ conn_emit_pending_crypto_data(ngtcp2_conn *conn,
   }

   for (;;) {
-    datalen = (size_t)ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset);
+    datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset);
     if (datalen == 0) {
       assert(rx_offset == ngtcp2_strm_rx_offset(strm));
       return 0;
@@ -6105,6 +6250,25 @@ static void conn_recv_path_challenge(ngtcp2_conn *conn, const ngtcp2_path *path,
  * conn_reset_congestion_state resets congestion state.
  */
 static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  /* If no packet sent since the previous reset has been
+     acknowledged, congestion state is still in its initial state,
+     and the reset would only forget the in-flight packets, which
+     stay in rtb until they are acknowledged or declared lost.  A
+     remote endpoint which keeps changing its address without sending
+     ACK would get a fresh initial congestion window per packet and
+     grow rtb without bound.  Keep the congestion state as we do for
+     a port-only change. */
+  if (conn->rst.delivered == 0 && conn->cstat.bytes_in_flight) {
+    conn->cstat.max_tx_udp_payload_size =
+      ngtcp2_conn_get_path_max_tx_udp_payload_size2(conn);
+
+    /* The timer might have been canceled due to amplification limit
+       on the previous path. */
+    ngtcp2_conn_set_loss_detection_timer(conn, ts);
+
+    return;
+  }
+
   conn_reset_conn_stat_cc(conn, &conn->cstat);

   if (conn->cc.reset) {
@@ -6158,8 +6322,8 @@ static int conn_server_preferred_addr_migration(const ngtcp2_conn *conn,
   return 0;
 }

-static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
-                                   ngtcp2_path_response *fr, ngtcp2_tstamp ts) {
+static int conn_recv_path_response(ngtcp2_conn *conn, ngtcp2_path_response *fr,
+                                   ngtcp2_tstamp ts) {
   int rv;
   ngtcp2_pv *pv = conn->pv, *npv = NULL;
   uint8_t ent_flags;
@@ -6194,12 +6358,6 @@ static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,

       conn_reset_congestion_state(conn, ts);
       conn_reset_ecn_validation_state(conn);
-
-      if (conn->server && conn->rx.preferred_addr.pkt_num == -1 &&
-          conn_server_preferred_addr_migration(
-            conn, &conn->dcid.current.ps.path.local)) {
-        conn->rx.preferred_addr.pkt_num = hd->pkt_num;
-      }
     }

     assert(ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path));
@@ -6251,9 +6409,9 @@ static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
   }

   if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) {
-    /* Unset the flag bit so that conn_stop_pv does not retire
+    /* Set the flag bit so that conn_stop_pv does not retire
        DCID. */
-    pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_PRESENT;
+    pv->flags |= NGTCP2_PV_FLAG_DONT_RETIRE_FALLBACK;
   }

   rv = conn_stop_pv(conn, ts);
@@ -6463,6 +6621,9 @@ static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn,
  *     Generic QUIC protocol error.
  * NGTCP2_ERR_INTERNAL
  *     Suspicious remote endpoint activity exceeded threshold.
+ * NGTCP2_ERR_AEAD_LIMIT_REACHED
+ *     The number of packets that fail authentication reached the
+ *     limit.
  *
  * In addition to the above error codes, error codes returned from
  * conn_recv_pkt are also returned.
@@ -6713,7 +6874,7 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
       }

       if (hd.version != conn->client_chosen_version &&
-          !conn->negotiated_version && conn->vneg.version != hd.version) {
+          !conn->negotiated_version) {
         if (!vneg_available_versions_includes(conn->vneg.available_versions,
                                               conn->vneg.available_versionslen,
                                               hd.version)) {
@@ -6723,7 +6884,7 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
         /* Install new Initial keys using QUIC version = hd.version */
         rv = conn_call_version_negotiation(
           conn, hd.version,
-          (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) ? &conn->dcid.current.cid
+          (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) ? &conn->retry_scid
                                                       : &conn->rcid);
         if (rv != 0) {
           return rv;
@@ -6850,6 +7011,14 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
     if (ngtcp2_err_is_fatal((int)nwrite)) {
       return nwrite;
     }
+
+    /* Initial keys are not secret, and there is no limit for them. */
+    if (hd.type == NGTCP2_PKT_HANDSHAKE &&
+        ++conn->crypto.decryption_failure_count >=
+          pktns->crypto.ctx.max_decryption_failure) {
+      return NGTCP2_ERR_AEAD_LIMIT_REACHED;
+    }
+
     ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
                     "could not decrypt packet payload");
     return NGTCP2_ERR_DISCARD_PKT;
@@ -6859,15 +7028,6 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
     return NGTCP2_ERR_PROTO;
   }

-  if (!conn->server && hd.version != conn->client_chosen_version &&
-      !conn->negotiated_version) {
-    conn->negotiated_version = hd.version;
-
-    ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON,
-                     "the negotiated version is 0x",
-                     hexw(conn->negotiated_version, 8));
-  }
-
   payload = conn->crypto.decrypt_buf.base;
   payloadlen = (size_t)nwrite;

@@ -6910,6 +7070,15 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
     return NGTCP2_ERR_PROTO;
   }

+  if (!conn->server && hd.version != conn->client_chosen_version &&
+      !conn->negotiated_version) {
+    conn->negotiated_version = hd.version;
+
+    ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON,
+                     "the negotiated version is 0x",
+                     hexw(conn->negotiated_version, 8));
+  }
+
   if (hd.type == NGTCP2_PKT_INITIAL &&
       !(conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED)) {
     conn->flags |= NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED;
@@ -7212,7 +7381,7 @@ fail:
  */
 static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm,
                                          uint64_t rx_offset) {
-  uint64_t datalen;
+  size_t datalen;
   const uint8_t *data;
   int rv;
   uint64_t offset;
@@ -7248,13 +7417,13 @@ static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm,
     if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
         rx_offset == strm->rx.last_offset) {
       sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN;
+      strm->flags |= NGTCP2_STRM_FLAG_RX_FIN_DELIVERED;
     }
     if (!handshake_completed) {
       sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT;
     }

-    rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data,
-                                    (size_t)datalen);
+    rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, datalen);
     if (rv != 0) {
       return rv;
     }
@@ -7457,31 +7626,30 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr,
   uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
   uint32_t sdflags = NGTCP2_STREAM_DATA_FLAG_NONE;
   ngtcp2_ssize nwrite;
-  int new_strm = 0;

   local_stream = conn_local_stream(conn, fr->stream_id);
   bidi = bidi_stream(fr->stream_id);

   if (bidi) {
     if (local_stream) {
-      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+      if (conn->tx.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_streams <
+    } else if (conn->rx.bidi.max_streams <
                ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.bidi.idtr;
+    idtr = &conn->bidi.idtr;
   } else {
     if (local_stream) {
       return NGTCP2_ERR_STREAM_STATE;
     }
-    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
+    if (conn->rx.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.uni.idtr;
+    idtr = &conn->uni.idtr;
   }

   if (NGTCP2_MAX_VARINT - datalen < fr->offset) {
@@ -7524,8 +7692,6 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr,
       return rv;
     }

-    new_strm = 1;
-
     rv = conn_call_stream_open(conn, strm);
     if (rv != 0) {
       return rv;
@@ -7566,18 +7732,10 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr,
   if (fr->fin) {
     if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) {
       if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) {
-        if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-          return NGTCP2_ERR_INTERNAL;
-        }
-
         return 0;
       }

       if (rx_offset == fr_end_offset) {
-        if (!new_strm && ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-          return NGTCP2_ERR_INTERNAL;
-        }
-
         return 0;
       }
     } else {
@@ -7588,19 +7746,8 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr,
   } else {
     strm->rx.last_offset = ngtcp2_max(strm->rx.last_offset, fr_end_offset);

-    if (fr_end_offset <= rx_offset) {
-      if (!new_strm && ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-        return NGTCP2_ERR_INTERNAL;
-      }
-
-      return 0;
-    }
-
-    if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) {
-      if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-        return NGTCP2_ERR_INTERNAL;
-      }
-
+    if (fr_end_offset <= rx_offset ||
+        (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED)) {
       return 0;
     }
   }
@@ -7633,6 +7780,7 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr,

       if (fin) {
         sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN;
+        strm->flags |= NGTCP2_STRM_FLAG_RX_FIN_DELIVERED;
       }
       if (!conn_is_tls_handshake_completed(conn)) {
         sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT;
@@ -7654,10 +7802,6 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr,
     if (nwrite < 0) {
       return (int)nwrite;
     }
-
-    if (nwrite == 0 && ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-      return NGTCP2_ERR_INTERNAL;
-    }
   }
   return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
 }
@@ -7678,13 +7822,7 @@ static int conn_reset_stream(ngtcp2_conn *conn, ngtcp2_strm *strm,
                  NGTCP2_STRM_FLAG_TX_RESET_STREAM_APP_ERROR_CODE_SET;
   strm->tx.reset_stream_app_error_code = app_error_code;

-  if (ngtcp2_strm_is_tx_queued(strm)) {
-    return 0;
-  }
-
-  strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
-
-  return ngtcp2_conn_tx_strmq_push(conn, strm);
+  return ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
 }

 /*
@@ -7703,13 +7841,7 @@ static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm,
                  NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET;
   strm->tx.stop_sending_app_error_code = app_error_code;

-  if (ngtcp2_strm_is_tx_queued(strm)) {
-    return 0;
-  }
-
-  strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
-
-  return ngtcp2_conn_tx_strmq_push(conn, strm);
+  return ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
 }

 /*
@@ -7739,13 +7871,11 @@ handle_max_remote_streams_extension(uint64_t *punsent_max_remote_streams,
  *
  * NGTCP2_ERR_STREAM_STATE
  *     RESET_STREAM frame is received to the local stream which is not
- *     initiated.
+ *     initiated; or RESET_STREAM frame is received to the local
+ *     unidirectional stream
  * NGTCP2_ERR_STREAM_LIMIT
  *     RESET_STREAM frame has remote stream ID which is strictly
  *     greater than the allowed limit.
- * NGTCP2_ERR_PROTO
- *     RESET_STREAM frame is received to the local unidirectional
- *     stream
  * NGTCP2_ERR_NOMEM
  *     Out of memory.
  * NGTCP2_ERR_CALLBACK_FAILURE
@@ -7771,24 +7901,24 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,
   /* TODO share this piece of code */
   if (bidi) {
     if (local_stream) {
-      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+      if (conn->tx.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_streams <
+    } else if (conn->rx.bidi.max_streams <
                ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.bidi.idtr;
+    idtr = &conn->bidi.idtr;
   } else {
     if (local_stream) {
-      return NGTCP2_ERR_PROTO;
+      return NGTCP2_ERR_STREAM_STATE;
     }
-    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
+    if (conn->rx.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.uni.idtr;
+    idtr = &conn->uni.idtr;
   }

   if (NGTCP2_MAX_VARINT < fr->final_size) {
@@ -7841,7 +7971,7 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,
     }
   }

-  if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD)) {
+  if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) {
     if (strm->rx.last_offset != fr->final_size) {
       return NGTCP2_ERR_FINAL_SIZE;
     }
@@ -7850,10 +7980,6 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,
   }

   if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) {
-    if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-      return NGTCP2_ERR_INTERNAL;
-    }
-
     return 0;
   }

@@ -7875,11 +8001,6 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,

   conn->rx.offset += datalen;

-  /* Extend connection flow control window for the amount of data
-     which are not passed to application. */
-  ngtcp2_conn_extend_max_offset(conn,
-                                fr->final_size - ngtcp2_strm_rx_offset(strm));
-
   strm->rx.last_offset = fr->final_size;
   strm->flags |= NGTCP2_STRM_FLAG_SHUT_RD |
                  NGTCP2_STRM_FLAG_RESET_STREAM_RECVED |
@@ -7922,21 +8043,21 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn,

   if (bidi) {
     if (local_stream) {
-      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+      if (conn->tx.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_streams <
+    } else if (conn->rx.bidi.max_streams <
                ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.bidi.idtr;
+    idtr = &conn->bidi.idtr;
   } else {
-    if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) {
+    if (!local_stream || conn->tx.uni.next_stream_id <= fr->stream_id) {
       return NGTCP2_ERR_STREAM_STATE;
     }

-    idtr = &conn->remote.uni.idtr;
+    idtr = &conn->uni.idtr;
   }

   strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
@@ -7981,10 +8102,6 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn,
   }

   if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING_RECVED) {
-    if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) {
-      return NGTCP2_ERR_INTERNAL;
-    }
-
     return 0;
   }

@@ -8013,7 +8130,7 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn,

   ngtcp2_strm_streamfrq_clear(strm);

-  return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+  return 0;
 }

 /*
@@ -8087,25 +8204,21 @@ static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path,
  */
 static int conn_recv_max_streams(ngtcp2_conn *conn,
                                  const ngtcp2_max_streams *fr) {
-  uint64_t n;
-
   if (fr->max_streams > NGTCP2_MAX_STREAMS) {
     return NGTCP2_ERR_FRAME_ENCODING;
   }

-  n = ngtcp2_min(fr->max_streams, NGTCP2_MAX_STREAMS);
-
   if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) {
-    if (conn->local.bidi.max_streams < n) {
-      conn->local.bidi.max_streams = n;
-      return conn_call_extend_max_local_streams_bidi(conn, n);
+    if (conn->tx.bidi.max_streams < fr->max_streams) {
+      conn->tx.bidi.max_streams = fr->max_streams;
+      return conn_call_extend_max_local_streams_bidi(conn, fr->max_streams);
     }
     return 0;
   }

-  if (conn->local.uni.max_streams < n) {
-    conn->local.uni.max_streams = n;
-    return conn_call_extend_max_local_streams_uni(conn, n);
+  if (conn->tx.uni.max_streams < fr->max_streams) {
+    conn->tx.uni.max_streams = fr->max_streams;
+    return conn_call_extend_max_local_streams_uni(conn, fr->max_streams);
   }
   return 0;
 }
@@ -8118,52 +8231,55 @@ static int conn_recv_max_streams(ngtcp2_conn *conn,
  * negative error codes:
  *
  * NGTCP2_ERR_PROTO
- *     |fr| has the duplicated sequence number with different CID or
- *     token; or DCID is zero-length.
+ *     |fr| contains the duplicated Connection ID when |fr|->seq is
+ *     unique; or DCID is zero-length.
  */
 static int conn_recv_new_connection_id(ngtcp2_conn *conn,
                                        const ngtcp2_new_connection_id *fr) {
   size_t len;
   ngtcp2_pv *pv = conn->pv;
   int rv;
-  int found = 0;
   size_t extra_dcid = 0;

   if (conn->dcid.current.cid.datalen == 0) {
     return NGTCP2_ERR_PROTO;
   }

-  if (fr->retire_prior_to > fr->seq) {
-    return NGTCP2_ERR_FRAME_ENCODING;
+  if (ngtcp2_gaptr_is_pushed(&conn->dcid.seqgap, fr->seq, 1)) {
+    return 0;
   }

-  rv = ngtcp2_dcid_verify_uniqueness(&conn->dcid.current, fr->seq, &fr->cid,
-                                     &fr->token);
+  rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, fr->seq, 1);
   if (rv != 0) {
     return rv;
   }
-  if (ngtcp2_cid_eq(&conn->dcid.current.cid, &fr->cid)) {
-    found = 1;
-  }

-  if (pv) {
-    rv =
-      ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, &fr->token);
-    if (rv != 0) {
-      return rv;
-    }
-    if (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid)) {
-      found = 1;
-    }
+  if (ngtcp2_ksl_len(&conn->dcid.seqgap.gap) > 32) {
+    ngtcp2_gaptr_drop_first_gap(&conn->dcid.seqgap);
   }

-  rv = ngtcp2_dcidtr_verify_token_uniqueness(&conn->dcid.dtr, &found, fr->seq,
-                                             &fr->cid, &fr->token);
-  if (rv != 0) {
-    return rv;
-  }
+  /* We now assume that fr->seq is unique. */

-  if (conn->dcid.retire_prior_to < fr->retire_prior_to) {
+  if (fr->retire_prior_to > fr->seq) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  if (ngtcp2_cid_eq(&conn->dcid.current.cid, &fr->cid)) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (pv && (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid) ||
+             ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+              ngtcp2_cid_eq(&pv->fallback_dcid.cid, &fr->cid)))) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  rv = ngtcp2_dcidtr_verify_cid_uniqueness(&conn->dcid.dtr, &fr->cid);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (conn->dcid.retire_prior_to < fr->retire_prior_to) {
     conn->dcid.retire_prior_to = fr->retire_prior_to;

     rv = ngtcp2_dcidtr_retire_inactive_dcid_prior_to(
@@ -8192,23 +8308,6 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn,
     return conn_enqueue_retire_connection_id(conn, fr->seq);
   }

-  if (found) {
-    return 0;
-  }
-
-  if (ngtcp2_gaptr_is_pushed(&conn->dcid.seqgap, fr->seq, 1)) {
-    return 0;
-  }
-
-  rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, fr->seq, 1);
-  if (rv != 0) {
-    return rv;
-  }
-
-  if (ngtcp2_ksl_len(&conn->dcid.seqgap.gap) > 32) {
-    ngtcp2_gaptr_drop_first_gap(&conn->dcid.seqgap);
-  }
-
   len = ngtcp2_dcidtr_inactive_len(&conn->dcid.dtr);

   if (conn->dcid.current.seq >= conn->dcid.retire_prior_to) {
@@ -8426,13 +8525,13 @@ static int conn_recv_new_token(ngtcp2_conn *conn, const ngtcp2_new_token *fr) {
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
- * NGTCP2_ERR_FRAME_ENCODING
+ * NGTCP2_ERR_STREAM_LIMIT
  *     Maximum Streams is larger than advertised value.
  */
 static int conn_recv_streams_blocked_bidi(ngtcp2_conn *conn,
                                           ngtcp2_streams_blocked *fr) {
-  if (fr->max_streams > conn->remote.bidi.max_streams) {
-    return NGTCP2_ERR_FRAME_ENCODING;
+  if (fr->max_streams > conn->rx.bidi.max_streams) {
+    return NGTCP2_ERR_STREAM_LIMIT;
   }

   return 0;
@@ -8445,13 +8544,13 @@ static int conn_recv_streams_blocked_bidi(ngtcp2_conn *conn,
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
- * NGTCP2_ERR_FRAME_ENCODING
+ * NGTCP2_ERR_STREAM_LIMIT
  *     Maximum Streams is larger than advertised value.
  */
 static int conn_recv_streams_blocked_uni(ngtcp2_conn *conn,
                                          ngtcp2_streams_blocked *fr) {
-  if (fr->max_streams > conn->remote.uni.max_streams) {
-    return NGTCP2_ERR_FRAME_ENCODING;
+  if (fr->max_streams > conn->rx.uni.max_streams) {
+    return NGTCP2_ERR_STREAM_LIMIT;
   }

   return 0;
@@ -8490,24 +8589,24 @@ static int conn_recv_stream_data_blocked(ngtcp2_conn *conn,

   if (bidi) {
     if (local_stream) {
-      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+      if (conn->tx.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_streams <
+    } else if (conn->rx.bidi.max_streams <
                ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.bidi.idtr;
+    idtr = &conn->bidi.idtr;
   } else {
     if (local_stream) {
       return NGTCP2_ERR_STREAM_STATE;
     }
-    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
+    if (conn->rx.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }

-    idtr = &conn->remote.uni.idtr;
+    idtr = &conn->uni.idtr;
   }

   strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
@@ -8542,11 +8641,11 @@ static int conn_recv_stream_data_blocked(ngtcp2_conn *conn,
     }
   }

-  if (strm->rx.max_offset < fr->offset) {
+  if (strm->rx.max_offset < fr->max_stream_data) {
     return NGTCP2_ERR_FLOW_CONTROL;
   }

-  if (fr->offset <= strm->rx.last_offset) {
+  if (fr->max_stream_data <= strm->rx.last_offset) {
     return 0;
   }

@@ -8554,7 +8653,7 @@ static int conn_recv_stream_data_blocked(ngtcp2_conn *conn,
     return NGTCP2_ERR_FINAL_SIZE;
   }

-  datalen = fr->offset - strm->rx.last_offset;
+  datalen = fr->max_stream_data - strm->rx.last_offset;
   if (datalen) {
     if (conn_max_data_violated(conn, datalen)) {
       return NGTCP2_ERR_FLOW_CONTROL;
@@ -8563,7 +8662,7 @@ static int conn_recv_stream_data_blocked(ngtcp2_conn *conn,
     conn->rx.offset += datalen;
   }

-  strm->rx.last_offset = fr->offset;
+  strm->rx.last_offset = fr->max_stream_data;

   return 0;
 }
@@ -8579,7 +8678,7 @@ static int conn_recv_stream_data_blocked(ngtcp2_conn *conn,
  *     It violates connection-level flow control limit.
  */
 static int conn_recv_data_blocked(ngtcp2_conn *conn, ngtcp2_data_blocked *fr) {
-  if (conn->rx.max_offset < fr->offset) {
+  if (conn->rx.max_offset < fr->max_data) {
     return NGTCP2_ERR_FLOW_CONTROL;
   }

@@ -8651,7 +8750,8 @@ static int conn_select_preferred_addr(ngtcp2_conn *conn) {
  * negative error codes:
  *
  * NGTCP2_ERR_PROTO
- *     Server received HANDSHAKE_DONE frame.
+ *     Server received HANDSHAKE_DONE frame; or client received it
+ *     before it could send Finished.
  * NGTCP2_ERR_NOMEM
  *     Out of memory.
  * NGTCP2_ERR_CALLBACK_FAILURE
@@ -8668,6 +8768,17 @@ static int conn_recv_handshake_done(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
     return 0;
   }

+  /* Server sends HANDSHAKE_DONE only after it has received client
+     Finished (RFC 9001 Section 4.1.2).  Client moves to
+     post-handshake state and discards Initial packet number space no
+     later than it sends Finished.  If either has not happened yet,
+     the remote endpoint is misbehaving, and Handshake packet number
+     space is still in use by conn_read_handshake and
+     conn_write_handshake, which must not see it discarded. */
+  if (conn->state != NGTCP2_CS_POST_HANDSHAKE || conn->in_pktns) {
+    return NGTCP2_ERR_PROTO;
+  }
+
   conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED |
                  NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED;

@@ -8836,6 +8947,33 @@ static void conn_rotate_keys(ngtcp2_conn *conn, int64_t pkt_num,
   }
 }

+/*
+ * conn_handle_unconfirmed_key_update_from_local deals with key update
+ * which has not been confirmed yet and initiated by the local
+ * endpoint.  |ckm| is the key which decrypted an incoming packet.
+ *
+ * If key update was initiated by the local endpoint, receiving a
+ * packet encrypted with the new key tells that the remote endpoint
+ * has updated its keys.  The remote endpoint may initiate the next
+ * key update as soon as that packet is acknowledged.  Do not wait for
+ * an acknowledgement for a packet that the local endpoint sent, which
+ * might be lost.
+ */
+static void conn_handle_unconfirmed_key_update_from_local(
+  ngtcp2_conn *conn, const ngtcp2_crypto_km *ckm, ngtcp2_tstamp ts) {
+  if (!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) ||
+      !(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) ||
+      ckm != conn->pktns.crypto.rx.ckm) {
+    return;
+  }
+
+  conn->flags &= (uint32_t)~(NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED |
+                             NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR);
+  conn->crypto.key_update.confirmed_ts = ts;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed");
+}
+
 /*
  * conn_path_validation_in_progress returns nonzero if path validation
  * against |path| is underway.  Ignore the path validation with
@@ -8850,23 +8988,57 @@ static int conn_path_validation_in_progress(const ngtcp2_conn *conn,
          !(pv->flags & NGTCP2_PV_FLAG_DONT_CARE);
 }

+/*
+ * conn_revert_client_migration handles the case that server observed
+ * that client is migrated back to the original path during the path
+ * validation or after that.
+ */
+static int conn_revert_client_migration(ngtcp2_conn *conn,
+                                        const ngtcp2_dcid *dcid,
+                                        size_t dgramlen, ngtcp2_tstamp ts) {
+  int rv;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV,
+                  "path is migrated back to the original path");
+
+  ngtcp2_dcid_copy(&conn->dcid.current, dcid);
+  conn_reset_congestion_state(conn, ts);
+  conn->dcid.current.bytes_recv += dgramlen;
+  conn_reset_ecn_validation_state(conn);
+
+  rv = conn_abort_pv(conn, ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Run PMTUD just in case if it is prematurely aborted */
+  if (!conn->pmtud && !conn->local.settings.no_pmtud) {
+    return conn_start_pmtud(conn);
+  }
+
+  return 0;
+}
+
 /*
  * conn_recv_non_probing_pkt_on_new_path is called when non-probing
  * packet is received via new path.  It starts path validation against
- * the new path.
+ * the new path.  |scid_seq| is the sequence number of the connection
+ * ID in Destination Connection ID field of the packet.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
  * NGTCP2_ERR_CONN_ID_BLOCKED
  *     No DCID is available
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  * NGTCP2_ERR_NOMEM
  *     Out of memory
  */
 static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
                                                  const ngtcp2_path *path,
                                                  size_t dgramlen,
-                                                 int new_cid_used,
+                                                 uint64_t scid_seq,
                                                  ngtcp2_tstamp ts) {
   ngtcp2_dcid dcid;
   ngtcp2_pv *pv = NULL;
@@ -8879,31 +9051,22 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,

   assert(conn->server);

-  if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
-      ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)) {
-    /* If new path equals fallback path, that means connection
-       migrated back to the original path.  Fallback path is
-       considered to be validated. */
-    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV,
-                    "path is migrated back to the original path");
-    ngtcp2_dcid_copy(&conn->dcid.current, &conn->pv->fallback_dcid);
-    conn_reset_congestion_state(conn, ts);
-    conn->dcid.current.bytes_recv += dgramlen;
-    conn_reset_ecn_validation_state(conn);
-
-    rv = conn_abort_pv(conn, ts);
-    if (rv != 0) {
-      return rv;
-    }
-
-    /* Run PMTUD just in case if it is prematurely aborted */
-    assert(!conn->pmtud);
-
-    if (!conn->local.settings.no_pmtud) {
-      return conn_start_pmtud(conn);
+  if (conn->pv) {
+    if ((conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+        ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)) {
+      /* If new path equals fallback path, that means connection
+         migrated back to the original path.  Fallback path is
+         considered to be validated. */
+      return conn_revert_client_migration(conn, &conn->pv->fallback_dcid,
+                                          dgramlen, ts);
+    }
+
+    if ((conn->pv->flags & NGTCP2_PV_FLAG_DONT_CARE) &&
+        ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)) {
+      /* Client migrated back to the original path after successful
+         validation. */
+      return conn_revert_client_migration(conn, &conn->pv->dcid, dgramlen, ts);
     }
-
-    return 0;
   }

   remote_addr_cmp =
@@ -8932,6 +9095,36 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
   ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
                   "non-probing packet was received from new remote address");

+  if (conn->pv) {
+    /* Stop the current path validation.  If the current path is not
+       validated, and pv has fallback path, set the fallback path to
+       the current path. */
+    if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) &&
+        (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT)) {
+      if (conn->dcid.current.cid.datalen &&
+          conn->dcid.current.seq != conn->pv->dcid.seq &&
+          conn->dcid.current.seq != conn->pv->fallback_dcid.seq) {
+        rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+
+      ngtcp2_dcid_copy(&conn->dcid.current, &conn->pv->fallback_dcid);
+      conn_reset_congestion_state(conn, ts);
+      conn_reset_ecn_validation_state(conn);
+    }
+
+    ngtcp2_log_info(
+      &conn->log, NGTCP2_LOG_EVENT_PTV,
+      "path migration is aborted because new migration has started");
+
+    rv = conn_abort_pv(conn, ts);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
   if (ngtcp2_dcidtr_pop_bound_dcid(&conn->dcid.dtr, &dcid, path) == 0) {
     ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
                     "Found DCID which has already been bound to the new path");
@@ -8944,7 +9137,8 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
     }
   } else {
     if (conn->dcid.current.cid.datalen &&
-        ((new_cid_used && remote_addr_cmp) || !local_addr_eq)) {
+        ((scid_seq != conn->dcid.current.scid_seq && remote_addr_cmp) ||
+         !local_addr_eq)) {
       /* New DCID is required. */
       if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
         return NGTCP2_ERR_CONN_ID_BLOCKED;
@@ -8963,12 +9157,22 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
       dcid.bytes_sent = 0;
       dcid.bytes_recv = 0;
       dcid.flags &= (uint8_t)~NGTCP2_DCID_FLAG_PATH_VALIDATED;
+
+      /* The copy carries the old path's max UDP payload size, which
+         may not hold for a new IP address; a port-only change
+         (e.g. NAT rebinding) keeps it, as it keeps congestion
+         state. */
+      if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_CMP_FLAG_ADDR |
+                                                NGTCP2_ADDR_CMP_FLAG_FAMILY))) {
+        dcid.max_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
+      }
     }

     ngtcp2_dcid_set_path(&dcid, path);
   }

   dcid.bytes_recv += dgramlen;
+  dcid.scid_seq = scid_seq;

   validated_path = ngtcp2_conn_find_path_history(conn, path, ts);
   if (!validated_path) {
@@ -8980,20 +9184,34 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
       return rv;
     }

-    if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT)) {
-      ngtcp2_pv_set_fallback(pv, &conn->pv->fallback_dcid,
-                             conn->pv->fallback_pto);
-      /* Unset the flag bit so that conn_stop_pv does not retire
-         DCID. */
-      conn->pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_PRESENT;
-    } else if (!pref_addr_migration) {
+    if (!pref_addr_migration) {
       ngtcp2_pv_set_fallback(pv, &conn->dcid.current, pto);
     }
+
+    conn->pv = pv;
   }

   if (!pref_addr_migration || validated_path) {
+    /* Restore the maximum UDP payload size of the validated path
+       before resetting congestion state because it derives its
+       parameters (e.g., send_quantum lower bound) from it. */
+    if (validated_path) {
+      ngtcp2_dcid_apply_validated_path(&dcid, validated_path);
+    }
+
     if (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) {
       ngtcp2_conn_add_path_history(conn, &conn->dcid.current, ts);
+      /* Now validated_path may point to the different object. */
+    }
+
+    if (conn->dcid.current.cid.datalen && dcid.seq != conn->dcid.current.seq &&
+        (!pv || (pv->dcid.seq != conn->dcid.current.seq &&
+                 (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) ||
+                  pv->fallback_dcid.seq != conn->dcid.current.seq)))) {
+      rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts);
+      if (rv != 0) {
+        return rv;
+      }
     }

     ngtcp2_dcid_copy(&conn->dcid.current, &dcid);
@@ -9001,37 +9219,30 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
     if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_CMP_FLAG_ADDR |
                                               NGTCP2_ADDR_CMP_FLAG_FAMILY))) {
       conn_reset_congestion_state(conn, ts);
+    } else {
+      /* Congestion state is kept, but the maximum UDP payload size of
+         the path may have changed. */
+      conn->cstat.max_tx_udp_payload_size =
+        ngtcp2_conn_get_path_max_tx_udp_payload_size2(conn);
     }

     conn_reset_ecn_validation_state(conn);

     ngtcp2_conn_stop_pmtud(conn);

-    if (validated_path) {
-      ngtcp2_dcid_apply_validated_path(&conn->dcid.current, validated_path);
-
-      if (!conn->local.settings.no_pmtud) {
-        rv = conn_start_pmtud(conn);
-        if (rv != 0) {
-          return rv;
-        }
+    if (validated_path && !conn->local.settings.no_pmtud) {
+      rv = conn_start_pmtud(conn);
+      if (rv != 0) {
+        return rv;
       }
     }
   }

   if (conn->pv) {
-    ngtcp2_log_info(
-      &conn->log, NGTCP2_LOG_EVENT_PTV,
-      "path migration is aborted because new migration has started");
-    rv = conn_abort_pv(conn, ts);
-    if (rv != 0) {
-      return rv;
-    }
+    return conn_call_begin_path_validation(conn, conn->pv);
   }

-  conn->pv = pv;
-
-  return conn_call_begin_path_validation(conn, conn->pv);
+  return 0;
 }

 /*
@@ -9045,6 +9256,8 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn,
  *
  * NGTCP2_ERR_CONN_ID_BLOCKED
  *     No unused DCID is available
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
  * NGTCP2_ERR_NOMEM
  *     Out of memory
  */
@@ -9244,6 +9457,9 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi,
  *     Frame has strictly larger end offset than it is permitted.
  * NGTCP2_ERR_INTERNAL
  *     Suspicious remote endpoint activity exceeded threshold.
+ * NGTCP2_ERR_AEAD_LIMIT_REACHED
+ *     The number of packets that fail authentication reached the
+ *     limit.
  */
 static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
                                   const ngtcp2_pkt_info *pi, const uint8_t *pkt,
@@ -9269,7 +9485,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
   int key_phase_bit_changed = 0;
   int force_decrypt_failure = 0;
   int recv_ncid = 0;
-  int new_cid_used = 0;
+  uint64_t scid_seq = 0;
   int path_challenge_recved = 0;
   size_t num_ack_processed = 0;

@@ -9336,7 +9552,13 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
         return NGTCP2_ERR_DISCARD_PKT;
       }

-      if (!conn->early.ckm) {
+      /* TLS stack might install 0-RTT key before it decides to send
+         HelloRetryRequest.  Local transport parameters are not
+         committed and remote transport parameters are not applied
+         until 1-RTT tx key is installed.  Do not process 0-RTT packet
+         until then.  Server which sends HelloRetryRequest must not
+         process 0-RTT packets anyway. */
+      if (!conn->early.ckm || !conn->pktns.crypto.tx.ckm) {
         return (ngtcp2_ssize)pktlen;
       }

@@ -9406,17 +9628,6 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
   ngtcp2_log_rx_pkt_hd(&conn->log, &hd);

   if (hd.type == NGTCP2_PKT_1RTT) {
-    if (conn->server && conn->rx.preferred_addr.pkt_num != -1 &&
-        conn->rx.preferred_addr.pkt_num < hd.pkt_num &&
-        ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&conn->hs_local_addr,
-                           path->local.addr)) {
-      ngtcp2_log_infof(
-        &conn->log, NGTCP2_LOG_EVENT_PKT, "pkt=", hd.pkt_num,
-        " is discarded because it was received on handshake local "
-        "address after preferred address migration");
-      return NGTCP2_ERR_DISCARD_PKT;
-    }
-
     key_phase_bit_changed = conn_key_phase_changed(conn, &hd);
   }

@@ -9469,9 +9680,12 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,

     assert(NGTCP2_ERR_DECRYPT == nwrite);

-    if (hd.type == NGTCP2_PKT_1RTT &&
-        ++conn->crypto.decryption_failure_count >=
-          pktns->crypto.ctx.max_decryption_failure) {
+    /* Packets that fail authentication are counted across all keys,
+       including Handshake and 0-RTT keys. */
+    if (++conn->crypto.decryption_failure_count >=
+        (hd.type == NGTCP2_PKT_0RTT
+           ? conn->early.ctx.max_decryption_failure
+           : pktns->crypto.ctx.max_decryption_failure)) {
       return NGTCP2_ERR_AEAD_LIMIT_REACHED;
     }

@@ -9546,7 +9760,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
       ngtcp2_unreachable();
     }
   } else {
-    rv = conn_verify_dcid(conn, &new_cid_used, &hd);
+    rv = conn_verify_dcid(conn, &scid_seq, &hd);
     if (rv != 0) {
       if (ngtcp2_err_is_fatal(rv)) {
         return rv;
@@ -9708,7 +9922,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
       path_challenge_recved = 1;
       break;
     case NGTCP2_FRAME_PATH_RESPONSE:
-      rv = conn_recv_path_response(conn, &hd, &fr.path_response, ts);
+      rv = conn_recv_path_response(conn, &fr.path_response, ts);
       if (rv != 0) {
         return rv;
       }
@@ -9800,28 +10014,27 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
       !ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
     if (non_probing_pkt && pktns->acktr.max_pkt_num < hd.pkt_num &&
         !conn_path_validation_in_progress(conn, path)) {
-      rv = conn_recv_non_probing_pkt_on_new_path(conn, path, dgramlen,
-                                                 new_cid_used, ts);
-      if (rv != 0) {
-        if (ngtcp2_err_is_fatal(rv)) {
-          return rv;
-        }
-
-        /* DCID is not available.  Just continue. */
-        assert(NGTCP2_ERR_CONN_ID_BLOCKED == rv);
+      rv = conn_recv_non_probing_pkt_on_new_path(conn, path, dgramlen, scid_seq,
+                                                 ts);
+      /* If DCID is not available, just continue. */
+      if (rv != 0 && rv != NGTCP2_ERR_CONN_ID_BLOCKED) {
+        return rv;
       }
     } else {
       rv = conn_recv_pkt_from_new_path(conn, path, dgramlen,
                                        path_challenge_recved, ts);
-      if (rv != 0) {
-        if (ngtcp2_err_is_fatal(rv)) {
-          return rv;
-        }
-
-        /* DCID is not available.  Just continue. */
-        assert(NGTCP2_ERR_CONN_ID_BLOCKED == rv);
+      /* If DCID is not available, just continue. */
+      if (rv != 0 && rv != NGTCP2_ERR_CONN_ID_BLOCKED) {
+        return rv;
       }
     }
+  } else if (!conn->server && hd.type == NGTCP2_PKT_1RTT && conn->pv &&
+             !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) &&
+             ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)) {
+    /* Account for the bytes received from the path which client is
+       validating so that PATH_RESPONSE sent to that path is subject
+       to the anti-amplification limit. */
+    conn->pv->dcid.bytes_recv += dgramlen;
   }

   if (hd.type == NGTCP2_PKT_1RTT) {
@@ -9832,12 +10045,18 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
       ckm->pkt_num = hd.pkt_num;
     }

+    conn_handle_unconfirmed_key_update_from_local(conn, ckm, ts);
+
     if (conn->server && conn->early.ckm &&
         conn->early.discard_started_ts == UINT64_MAX) {
       conn->early.discard_started_ts = ts;
     }

     if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
+      if (pktns->acktr.max_pkt_num < hd.pkt_num) {
+        conn->dcid.current.scid_seq = scid_seq;
+      }
+
       conn_update_keep_alive_last_ts(conn, ts);
     }
   }
@@ -9949,8 +10168,8 @@ static void conn_sync_stream_id_limit(ngtcp2_conn *conn) {

   assert(params);

-  conn->local.bidi.max_streams = params->initial_max_streams_bidi;
-  conn->local.uni.max_streams = params->initial_max_streams_uni;
+  conn->tx.bidi.max_streams = params->initial_max_streams_bidi;
+  conn->tx.uni.max_streams = params->initial_max_streams_uni;
 }

 static int strm_set_max_offset(void *data, void *ptr) {
@@ -10014,16 +10233,22 @@ static int conn_handshake_completed(ngtcp2_conn *conn) {
     return rv;
   }

-  if (conn->local.bidi.max_streams > 0) {
-    rv = conn_call_extend_max_local_streams_bidi(conn,
-                                                 conn->local.bidi.max_streams);
+  if (conn->tx.bidi.max_streams > 0) {
+    rv =
+      conn_call_extend_max_local_streams_bidi(conn, conn->tx.bidi.max_streams);
     if (rv != 0) {
       return rv;
     }
   }
-  if (conn->local.uni.max_streams > 0) {
-    rv =
-      conn_call_extend_max_local_streams_uni(conn, conn->local.uni.max_streams);
+  if (conn->tx.uni.max_streams > 0) {
+    rv = conn_call_extend_max_local_streams_uni(conn, conn->tx.uni.max_streams);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (conn->tx.max_offset > 0) {
+    rv = conn_call_extend_max_data(conn, conn->tx.max_offset);
     if (rv != 0) {
       return rv;
     }
@@ -10290,6 +10515,7 @@ static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn,
     if (rv != 0) {
       return rv;
     }
+
     conn->state = NGTCP2_CS_POST_HANDSHAKE;

     rv = conn_call_activate_dcid(conn, &conn->dcid.current);
@@ -10342,8 +10568,6 @@ int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path,
   const ngtcp2_pkt_info zero_pi = {0};
   (void)pkt_info_version;

-  assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING));
-
   conn_update_timestamp(conn, ts);

   ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON,
@@ -10431,7 +10655,24 @@ int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path,
     ngtcp2_unreachable();
   }

-  return conn_recv_cpkt(conn, path, pi, pkt, pktlen, ts);
+  rv = conn_recv_cpkt(conn, path, pi, pkt, pktlen, ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* If server hits amplification limit, it cancels loss detection
+     timer.  The limit is lifted if server receives more bytes on the
+     current path, the path gets validated, or server migrates to
+     another path.  Server does not necessarily send an ack-eliciting
+     packet after that (e.g., it is congestion limited).  Re-arm the
+     timer here, otherwise in-flight packets are never declared
+     lost. */
+  if (conn->server && conn->cstat.loss_detection_timer == UINT64_MAX &&
+      conn_server_tx_left(conn, &conn->dcid.current)) {
+    ngtcp2_conn_set_loss_detection_timer(conn, ts);
+  }
+
+  return 0;
 }

 int ngtcp2_conn_continue_handshake(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
@@ -10578,7 +10819,6 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
   ngtcp2_ssize res = 0, nwrite = 0, early_spktlen = 0;
   size_t origlen = destlen;
   uint64_t pending_early_datalen;
-  ngtcp2_preferred_addr *paddr;
   ngtcp2_stateless_reset_token token;

   switch (conn->state) {
@@ -10703,19 +10943,6 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,

     assert(conn->remote.transport_params);

-    if (conn->remote.transport_params->preferred_addr_present) {
-      assert(!ngtcp2_dcidtr_unused_full(&conn->dcid.dtr));
-
-      paddr = &conn->remote.transport_params->preferred_addr;
-      memcpy(token.data, paddr->stateless_reset_token, sizeof(token.data));
-      ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, 1, &paddr->cid, &token);
-
-      rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, 1, 1);
-      if (rv != 0) {
-        return (ngtcp2_ssize)rv;
-      }
-    }
-
     if (conn->remote.transport_params->stateless_reset_token_present &&
         conn->dcid.current.seq == 0) {
       assert(!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT));
@@ -10902,6 +11129,7 @@ conn_client_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
         break;
       }
       /* fall through */
+    case NGTCP2_ERR_STREAM_SHUT_WR:
     case NGTCP2_ERR_WRITE_MORE:
       conn->pkt.hs_spktlen = spktlen;
       break;
@@ -11160,8 +11388,22 @@ int ngtcp2_conn_install_0rtt_key(ngtcp2_conn *conn,
   int rv;

   assert(ivlen >= 8);
-  assert(!conn->early.hp_ctx.native_handle);
-  assert(!conn->early.ckm);
+
+  /* Some TLS stack (e.g., OpenSSL 3.6) attempts to install 0RTT key
+     twice when it receives HRR from server.  After HRR, TLS client
+     cannot send 0RTT data, 0RTT key is useless.*/
+  if (conn->early.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx);
+    ngtcp2_crypto_km_del(conn->early.ckm, conn->mem);
+    conn->early.ckm = NULL;
+  }
+
+  if (conn->early.hp_ctx.native_handle) {
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx);
+    conn->early.hp_ctx = (ngtcp2_crypto_cipher_ctx){0};
+  }
+
+  conn->flags &= ~NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED;

   rv = ngtcp2_crypto_km_new(&conn->early.ckm, NULL, 0, aead_ctx, iv, ivlen,
                             conn->mem);
@@ -11198,8 +11440,12 @@ int ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret,
   int rv;

   assert(ivlen >= 8);
-  assert(!pktns->crypto.rx.hp_ctx.native_handle);
-  assert(!pktns->crypto.rx.ckm);
+
+  /* Graceful connection closure for TLS stack does not follow RFC
+     9001, section 6. */
+  if (pktns->crypto.rx.hp_ctx.native_handle || pktns->crypto.rx.ckm) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }

   rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, secret, secretlen, aead_ctx,
                             iv, ivlen, conn->mem);
@@ -11245,8 +11491,11 @@ int ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret,
   int rv;

   assert(ivlen >= 8);
-  assert(!pktns->crypto.tx.hp_ctx.native_handle);
-  assert(!pktns->crypto.tx.ckm);
+
+  /* See the comment in ngtcp2_conn_install_rx_key. */
+  if (pktns->crypto.tx.hp_ctx.native_handle || pktns->crypto.tx.ckm) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }

   rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, secret, secretlen, aead_ctx,
                             iv, ivlen, conn->mem);
@@ -11285,9 +11534,8 @@ static int conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   ngtcp2_tstamp confirmed_ts = conn->crypto.key_update.confirmed_ts;
   ngtcp2_duration pto = ngtcp2_conn_compute_pto(conn, &conn->pktns);

-  assert(conn->state == NGTCP2_CS_POST_HANDSHAKE);
-
-  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) ||
+  if (conn->state != NGTCP2_CS_POST_HANDSHAKE ||
+      !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) ||
       (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) ||
       !conn->crypto.key_update.new_tx_ckm ||
       !conn->crypto.key_update.new_rx_ckm ||
@@ -11295,7 +11543,18 @@ static int conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
     return NGTCP2_ERR_INVALID_STATE;
   }

-  conn_rotate_keys(conn, NGTCP2_MAX_PKT_NUM, /* initiator = */ 1);
+  /* A subsequent key update must not be initiated until a packet
+     encrypted with the current key is acknowledged. */
+  if (confirmed_ts != UINT64_MAX && conn->pktns.rtb.largest_acked_tx_pkt_num <
+                                      conn->pktns.crypto.tx.ckm->pkt_num) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  /* No packet protected by the new key has been received yet.  Use
+     INT64_MAX instead of NGTCP2_MAX_PKT_NUM because the latter is a
+     valid packet number that the remote endpoint can send with the
+     old key. */
+  conn_rotate_keys(conn, INT64_MAX, /* initiator = */ 1);

   return 0;
 }
@@ -11395,8 +11654,6 @@ int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) {

   pto = ngtcp2_conn_compute_pto(conn, &conn->pktns);

-  assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING));
-
   if (ngtcp2_conn_get_idle_expiry(conn) <= ts) {
     return NGTCP2_ERR_IDLE_CLOSE;
   }
@@ -11410,7 +11667,21 @@ int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   ngtcp2_conn_remove_lost_pkt(conn, ts);

   if (conn->pv) {
-    ngtcp2_pv_cancel_expired_timer(conn->pv, ts);
+    /* conn_write_path_challenge also checks the timeout, but it might
+       not get a chance to send another PATH_CHALLENGE (e.g., server
+       is blocked by anti-amplification limit, or congestion window is
+       depleted).  Path validation must time out regardless. */
+    if (ngtcp2_pv_validation_timed_out(conn->pv, ts)) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV,
+                      "path validation was timed out");
+
+      rv = conn_on_path_validation_failed(conn, conn->pv, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    } else {
+      ngtcp2_pv_cancel_expired_timer(conn->pv, ts);
+    }
   }

   if (conn->pmtud) {
@@ -11509,14 +11780,17 @@ void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts) {

   if (conn->in_pktns) {
     timeout = ngtcp2_conn_compute_pto(conn, conn->in_pktns) * 3;
-    ngtcp2_rtb_remove_expired_lost_pkt(&conn->in_pktns->rtb, timeout, ts);
+    ngtcp2_rtb_remove_expired_lost_pkt(&conn->in_pktns->rtb, timeout,
+                                       &conn->cstat, ts);
   }
   if (conn->hs_pktns) {
     timeout = ngtcp2_conn_compute_pto(conn, conn->hs_pktns) * 3;
-    ngtcp2_rtb_remove_expired_lost_pkt(&conn->hs_pktns->rtb, timeout, ts);
+    ngtcp2_rtb_remove_expired_lost_pkt(&conn->hs_pktns->rtb, timeout,
+                                       &conn->cstat, ts);
   }
   timeout = ngtcp2_conn_compute_pto(conn, &conn->pktns) * 3;
-  ngtcp2_rtb_remove_expired_lost_pkt(&conn->pktns.rtb, timeout, ts);
+  ngtcp2_rtb_remove_expired_lost_pkt(&conn->pktns.rtb, timeout, &conn->cstat,
+                                     ts);
 }

 /*
@@ -11667,6 +11941,8 @@ ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn,

 int ngtcp2_conn_set_remote_transport_params(
   ngtcp2_conn *conn, const ngtcp2_transport_params *params) {
+  const ngtcp2_preferred_addr *paddr;
+  ngtcp2_stateless_reset_token token;
   int rv;

   /* We expect this function is called once per QUIC connection, but
@@ -11743,6 +12019,22 @@ int ngtcp2_conn_set_remote_transport_params(
     if (rv != 0) {
       return rv;
     }
+
+    /* Server can send NEW_CONNECTION_ID frames in 0.5 RTT packets.
+       Store the Connection ID in preferred_address now so that those
+       frames are verified against it. */
+    if (params->preferred_addr_present) {
+      assert(!ngtcp2_dcidtr_unused_full(&conn->dcid.dtr));
+
+      rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, 1, 1);
+      if (rv != 0) {
+        return rv;
+      }
+
+      paddr = &params->preferred_addr;
+      memcpy(token.data, paddr->stateless_reset_token, sizeof(token.data));
+      ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, 1, &paddr->cid, &token);
+    }
   }

   ngtcp2_log_remote_tp(&conn->log, params);
@@ -11981,10 +12273,10 @@ int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn) {

   conn->rx.window = conn->rx.unsent_max_offset = conn->rx.max_offset =
     params->initial_max_data;
-  conn->remote.bidi.unsent_max_streams = params->initial_max_streams_bidi;
-  conn->remote.bidi.max_streams = params->initial_max_streams_bidi;
-  conn->remote.uni.unsent_max_streams = params->initial_max_streams_uni;
-  conn->remote.uni.max_streams = params->initial_max_streams_uni;
+  conn->rx.bidi.unsent_max_streams = params->initial_max_streams_bidi;
+  conn->rx.bidi.max_streams = params->initial_max_streams_bidi;
+  conn->rx.uni.unsent_max_streams = params->initial_max_streams_uni;
+  conn->rx.uni.max_streams = params->initial_max_streams_uni;

   conn->flags |= NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED;

@@ -12031,15 +12323,15 @@ int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, int64_t *pstream_id,
     return NGTCP2_ERR_NOMEM;
   }

-  rv = ngtcp2_conn_init_stream(conn, strm, conn->local.bidi.next_stream_id,
+  rv = ngtcp2_conn_init_stream(conn, strm, conn->tx.bidi.next_stream_id,
                                stream_user_data);
   if (rv != 0) {
     ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
     return rv;
   }

-  *pstream_id = conn->local.bidi.next_stream_id;
-  conn->local.bidi.next_stream_id += 4;
+  *pstream_id = conn->tx.bidi.next_stream_id;
+  conn->tx.bidi.next_stream_id += 4;

   return 0;
 }
@@ -12058,14 +12350,14 @@ int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, int64_t *pstream_id,
     return NGTCP2_ERR_NOMEM;
   }

-  rv = ngtcp2_conn_init_stream(conn, strm, conn->local.uni.next_stream_id,
+  rv = ngtcp2_conn_init_stream(conn, strm, conn->tx.uni.next_stream_id,
                                stream_user_data);
   if (rv != 0) {
     ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
     return rv;
   }
-  *pstream_id = conn->local.uni.next_stream_id;
-  conn->local.uni.next_stream_id += 4;
+  *pstream_id = conn->tx.uni.next_stream_id;
+  conn->tx.uni.next_stream_id += 4;

   return 0;
 }
@@ -12080,22 +12372,13 @@ ngtcp2_ssize ngtcp2_conn_write_stream_versioned(
   ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen,
   uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen,
   ngtcp2_tstamp ts) {
-  ngtcp2_vec datav, *v;
-  size_t datacnt;
-
-  if (datalen == 0) {
-    v = NULL;
-    datacnt = 0;
-  } else {
-    datav.len = datalen;
-    datav.base = (uint8_t *)data;
-    v = &datav;
-    datacnt = 1;
-  }
-
-  return ngtcp2_conn_writev_stream_versioned(conn, path, pkt_info_version, pi,
-                                             dest, destlen, pdatalen, flags,
-                                             stream_id, v, datacnt, ts);
+  return ngtcp2_conn_writev_stream_versioned(
+    conn, path, pkt_info_version, pi, dest, destlen, pdatalen, flags, stream_id,
+    &(ngtcp2_vec){
+      .base = (uint8_t *)data,
+      .len = datalen,
+    },
+    datalen != 0, ts);
 }

 static int conn_no_app_data_written(const ngtcp2_conn *conn) {
@@ -12610,6 +12893,7 @@ fin:
       break;
     }
     /* fall through */
+  case NGTCP2_ERR_STREAM_SHUT_WR:
   case NGTCP2_ERR_WRITE_MORE:
     conn->pkt.hs_spktlen = res;
     break;
@@ -12636,7 +12920,7 @@ conn_write_connection_close(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
     .reason = (uint8_t *)reason,
   };

-  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) &&
+  if ((conn->server || !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) &&
       pkt_type != NGTCP2_PKT_INITIAL) {
     if (in_pktns && conn->server) {
       nwrite = ngtcp2_conn_write_single_frame_pkt(
@@ -12759,6 +13043,7 @@ ngtcp2_ssize ngtcp2_conn_write_application_close_pkt(
   ngtcp2_ssize res = 0;
   ngtcp2_frame fr;
   uint64_t server_tx_left;
+  uint8_t pkt_type;

   if (conn_check_pkt_num_exhausted(conn)) {
     return NGTCP2_ERR_PKT_NUM_EXHAUSTED;
@@ -12789,17 +13074,26 @@ ngtcp2_ssize ngtcp2_conn_write_application_close_pkt(
     destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left);
   }

-  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) {
-    nwrite = conn_write_connection_close(
-      conn, pi, dest, destlen,
-      conn->hs_pktns->crypto.tx.ckm ? NGTCP2_PKT_HANDSHAKE : NGTCP2_PKT_INITIAL,
-      NGTCP2_APPLICATION_ERROR, NULL, 0, ts);
-    if (nwrite < 0) {
-      return nwrite;
+  if (conn->server || !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) {
+    if (conn->hs_pktns && conn->hs_pktns->crypto.tx.ckm) {
+      pkt_type = NGTCP2_PKT_HANDSHAKE;
+    } else if (conn->in_pktns && conn->in_pktns->crypto.tx.ckm) {
+      pkt_type = NGTCP2_PKT_INITIAL;
+    } else {
+      pkt_type = NGTCP2_PKT_1RTT;
+    }
+
+    if (pkt_type != NGTCP2_PKT_1RTT) {
+      nwrite =
+        conn_write_connection_close(conn, pi, dest, destlen, pkt_type,
+                                    NGTCP2_APPLICATION_ERROR, NULL, 0, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+      res = nwrite;
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
     }
-    res = nwrite;
-    dest += nwrite;
-    destlen -= (size_t)nwrite;
   }

   if (conn->state != NGTCP2_CS_POST_HANDSHAKE &&
@@ -12952,6 +13246,11 @@ int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm) {
     return rv;
   }

+  /* Extend connection flow control window for the amount of data
+     which are not passed to application. */
+  ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset -
+                                        ngtcp2_strm_rx_offset(strm));
+
   if (ngtcp2_strm_is_tx_queued(strm)) {
     ngtcp2_pq_remove(&conn->tx.strmq, &strm->pe);
   }
@@ -13022,15 +13321,16 @@ static int conn_shutdown_stream_read(ngtcp2_conn *conn, ngtcp2_strm *strm,
       (NGTCP2_STRM_FLAG_STOP_SENDING | NGTCP2_STRM_FLAG_RESET_STREAM_RECVED)) {
     return 0;
   }
-  if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
-      ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) {
+  /* ngtcp2_strm_rx_offset() must not be used here.  If this function
+     is called inside ngtcp2_callbacks.recv_stream_data, all stream
+     data might have been received, but some of them might still be
+     buffered, and have not been passed to an application yet. */
+  if (strm->flags & NGTCP2_STRM_FLAG_RX_FIN_DELIVERED) {
     return 0;
   }

   strm->flags |= NGTCP2_STRM_FLAG_STOP_SENDING;

-  ngtcp2_strm_stop_buffering_reordered_data(strm);
-
   return conn_stop_sending(conn, strm, app_error_code);
 }

@@ -13110,8 +13410,6 @@ int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, uint32_t flags,
  */
 static int conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm,
                                          uint64_t datalen) {
-  ngtcp2_strm *top;
-
   if (datalen > NGTCP2_MAX_VARINT ||
       strm->rx.unsent_max_offset > NGTCP2_MAX_VARINT - datalen) {
     strm->rx.unsent_max_offset = NGTCP2_MAX_VARINT;
@@ -13121,14 +13419,8 @@ static int conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm,

   if (!(strm->flags &
         (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) &&
-      !ngtcp2_strm_is_tx_queued(strm) &&
       conn_should_send_max_stream_data(conn, strm)) {
-    if (!ngtcp2_pq_empty(&conn->tx.strmq)) {
-      top = ngtcp2_conn_tx_strmq_top(conn);
-      strm->cycle = top->cycle;
-    }
-    strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
-    return ngtcp2_conn_tx_strmq_push(conn, strm);
+    return ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
   }

   return 0;
@@ -13161,11 +13453,11 @@ void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, uint64_t datalen) {
 }

 void ngtcp2_conn_extend_max_streams_bidi(ngtcp2_conn *conn, size_t n) {
-  handle_max_remote_streams_extension(&conn->remote.bidi.unsent_max_streams, n);
+  handle_max_remote_streams_extension(&conn->rx.bidi.unsent_max_streams, n);
 }

 void ngtcp2_conn_extend_max_streams_uni(ngtcp2_conn *conn, size_t n) {
-  handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams, n);
+  handle_max_remote_streams_extension(&conn->rx.uni.unsent_max_streams, n);
 }

 const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn) {
@@ -13217,11 +13509,14 @@ static int delete_strms_pq_each(void *data, void *ptr) {

 /*
  * conn_discard_early_data_state discards any connection states which
- * are altered by any operations during early data transfer.
+ * are altered by any operations during early data transfer.  This
+ * function assumes that |conn| is initialized as client.
  */
 static void conn_discard_early_data_state(ngtcp2_conn *conn) {
   ngtcp2_frame_chain **pfrc, *frc;

+  assert(!conn->server);
+
   ngtcp2_rtb_remove_early_data(&conn->pktns.rtb, &conn->cstat);

   ngtcp2_map_each(&conn->strms, delete_strms_pq_each, conn);
@@ -13233,19 +13528,14 @@ static void conn_discard_early_data_state(ngtcp2_conn *conn) {
   conn->rx.unsent_max_offset = conn->rx.max_offset =
     conn->local.transport_params.initial_max_data;

-  conn->remote.bidi.unsent_max_streams = conn->remote.bidi.max_streams =
+  conn->rx.bidi.unsent_max_streams = conn->rx.bidi.max_streams =
     conn->local.transport_params.initial_max_streams_bidi;

-  conn->remote.uni.unsent_max_streams = conn->remote.uni.max_streams =
+  conn->rx.uni.unsent_max_streams = conn->rx.uni.max_streams =
     conn->local.transport_params.initial_max_streams_uni;

-  if (conn->server) {
-    conn->local.bidi.next_stream_id = 1;
-    conn->local.uni.next_stream_id = 3;
-  } else {
-    conn->local.bidi.next_stream_id = 0;
-    conn->local.uni.next_stream_id = 2;
-  }
+  conn->tx.bidi.next_stream_id = 0;
+  conn->tx.uni.next_stream_id = 2;

   for (pfrc = &conn->pktns.tx.frq; *pfrc;) {
     frc = *pfrc;
@@ -13255,6 +13545,8 @@ static void conn_discard_early_data_state(ngtcp2_conn *conn) {
 }

 int ngtcp2_conn_tls_early_data_rejected(ngtcp2_conn *conn) {
+  assert(!conn->server);
+
   if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) {
     return 0;
   }
@@ -13263,14 +13555,14 @@ int ngtcp2_conn_tls_early_data_rejected(ngtcp2_conn *conn) {

   conn_discard_early_data_state(conn);

-  if (conn->callbacks.tls_early_data_rejected) {
-    return conn->callbacks.tls_early_data_rejected(conn, conn->user_data);
-  }
-
   if (conn->early.ckm) {
     conn_discard_early_key(conn);
   }

+  if (conn->callbacks.tls_early_data_rejected) {
+    return conn->callbacks.tls_early_data_rejected(conn, conn->user_data);
+  }
+
   return 0;
 }

@@ -13662,6 +13954,16 @@ int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm) {
   return ngtcp2_pq_push(&conn->tx.strmq, &strm->pe);
 }

+int ngtcp2_conn_tx_strmq_push_if_not(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  if (ngtcp2_strm_is_tx_queued(strm)) {
+    return 0;
+  }
+
+  strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
+
+  return ngtcp2_conn_tx_strmq_push(conn, strm);
+}
+
 static int conn_has_uncommitted_preferred_addr_cid(const ngtcp2_conn *conn) {
   return conn->server &&
          !(conn->flags & NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED) &&
@@ -13906,13 +14208,18 @@ int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn,

   ngtcp2_dcid_copy(&conn->dcid.current, &dcid);

-  conn_reset_congestion_state(conn, ts);
-  conn_reset_ecn_validation_state(conn);
-
+  /* Restore the maximum UDP payload size of the validated path
+     before resetting congestion state because it derives its
+     parameters (e.g., send_quantum lower bound) from it. */
   validated_path = ngtcp2_conn_find_path_history(conn, path, ts);
   if (validated_path) {
     ngtcp2_dcid_apply_validated_path(&conn->dcid.current, validated_path);
+  }
+
+  conn_reset_congestion_state(conn, ts);
+  conn_reset_ecn_validation_state(conn);

+  if (validated_path) {
     if (!conn->local.settings.no_pmtud) {
       rv = conn_start_pmtud(conn);
       if (rv != 0) {
@@ -13994,6 +14301,16 @@ uint64_t ngtcp2_conn_get_max_data_left2(const ngtcp2_conn *conn) {
   return conn->tx.max_offset - conn->tx.offset;
 }

+uint64_t ngtcp2_conn_get_max_data_left_write_thresh(const ngtcp2_conn *conn) {
+  uint64_t n = conn->tx.max_offset - conn->tx.offset;
+
+  if (n <= conn->local.settings.write_stream_data_thresh) {
+    return 0;
+  }
+
+  return n;
+}
+
 uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn,
                                               int64_t stream_id) {
   return ngtcp2_conn_get_max_stream_data_left2(conn, stream_id);
@@ -14015,11 +14332,9 @@ uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn) {
 }

 uint64_t ngtcp2_conn_get_streams_bidi_left2(const ngtcp2_conn *conn) {
-  uint64_t n = ngtcp2_ord_stream_id(conn->local.bidi.next_stream_id);
+  uint64_t n = ngtcp2_ord_stream_id(conn->tx.bidi.next_stream_id);

-  return n > conn->local.bidi.max_streams
-           ? 0
-           : conn->local.bidi.max_streams - n + 1;
+  return n > conn->tx.bidi.max_streams ? 0 : conn->tx.bidi.max_streams - n + 1;
 }

 uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn) {
@@ -14027,10 +14342,9 @@ uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn) {
 }

 uint64_t ngtcp2_conn_get_streams_uni_left2(const ngtcp2_conn *conn) {
-  uint64_t n = ngtcp2_ord_stream_id(conn->local.uni.next_stream_id);
+  uint64_t n = ngtcp2_ord_stream_id(conn->tx.uni.next_stream_id);

-  return n > conn->local.uni.max_streams ? 0
-                                         : conn->local.uni.max_streams - n + 1;
+  return n > conn->tx.uni.max_streams ? 0 : conn->tx.uni.max_streams - n + 1;
 }

 uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn) {
@@ -14267,7 +14581,12 @@ size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn) {
 }

 size_t ngtcp2_conn_get_send_quantum2(const ngtcp2_conn *conn) {
-  return conn->cstat.send_quantum;
+  /* Congestion controller updates send_quantum only when it processes
+     ACK, but the maximum UDP payload size of the current path may
+     grow in between (e.g., migrating back to the validated path).
+     Ensure that at least one full-sized packet fits. */
+  return ngtcp2_max(conn->cstat.send_quantum,
+                    ngtcp2_conn_get_path_max_tx_udp_payload_size2(conn));
 }

 size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, int64_t stream_id) {
@@ -14328,6 +14647,7 @@ ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt2_versioned(
   int first_pkt;
   ngtcp2_pkt_info pi_discard;
   ngtcp2_path_storage path_discard;
+  ngtcp2_path_storage path_start;
   (void)pkt_info_version;

   assert(buflen >= path_max_udp_payloadlen);
@@ -14336,6 +14656,8 @@ ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt2_versioned(
     num_pkts = SIZE_MAX;
   }

+  ngtcp2_path_storage_init2(&path_start, &conn->dcid.current.ps.path);
+
   for (;;) {
     ecn_state = conn->tx.ecn.state;

@@ -14365,7 +14687,8 @@ ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt2_versioned(

       if ((size_t)nwrite != path_max_udp_payloadlen ||
           buflen < path_max_udp_payloadlen || ecn_state != conn->tx.ecn.state ||
-          num_pkts == 0) {
+          num_pkts == 0 || (path && !ngtcp2_path_eq(path, &path_start.path)) ||
+          !ngtcp2_path_eq(&conn->dcid.current.ps.path, &path_start.path)) {
         nwrite = wbuf - buf;
         break;
       }
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h
index 18af11727e0..5fd9bad9da3 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h
@@ -93,6 +93,10 @@ typedef enum {
    value, it is truncated. */
 #define NGTCP2_CCERR_MAX_REASONLEN 1024

+/* NGTCP2_MAX_TOKENLEN is the maximum length of token given in
+   ngtcp2_settings.token that client sends in its Initial packet. */
+#define NGTCP2_MAX_TOKENLEN 512
+
 /* NGTCP2_WRITE_PKT_FLAG_NONE indicates that no flag is set. */
 #define NGTCP2_WRITE_PKT_FLAG_NONE 0x00U
 /* NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING indicates that packet other
@@ -164,9 +168,9 @@ void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent,
    supports Greasing QUIC Bit extension. */
 #define NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT 0x0400U
 /* NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED is set when key update is
-   not confirmed by the local endpoint.  That is, it has not received
-   ACK frame which acknowledges packet which is encrypted with new
-   key. */
+   not confirmed by the local endpoint.  That is, it has received
+   neither ACK frame which acknowledges packet which is encrypted with
+   new key, nor packet which is encrypted with new key. */
 #define NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED 0x0800U
 /* NGTCP2_CONN_FLAG_PPE_PENDING is set when
    NGTCP2_WRITE_STREAM_FLAG_MORE is used and the intermediate state of
@@ -193,6 +197,9 @@ void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent,
    Initial CRYPTO frame into pieces as a countermeasure against Deep
    Packet Inspection. */
 #define NGTCP2_CONN_FLAG_CRUMBLE_INITIAL_CRYPTO 0x40000U
+/* NGTCP2_CONN_FLAG_CLIENT_INITIAL_CALLED is set when
+   ngtcp2_callbacks.client_initial has been called successfully. */
+#define NGTCP2_CONN_FLAG_CLIENT_INITIAL_CALLED 0x80000U

 typedef struct ngtcp2_pktns {
   struct {
@@ -391,6 +398,24 @@ struct ngtcp2_conn {
        sent. */
     ngtcp2_tstamp last_max_data_ts;

+    struct {
+      /* max_streams is the maximum number of bidirectional streams which
+         the local endpoint can open. */
+      uint64_t max_streams;
+      /* next_stream_id is the bidirectional stream ID which the local
+         endpoint opens next. */
+      int64_t next_stream_id;
+    } bidi;
+
+    struct {
+      /* max_streams is the maximum number of unidirectional streams
+         which the local endpoint can open. */
+      uint64_t max_streams;
+      /* next_stream_id is the unidirectional stream ID which the
+         local endpoint opens next. */
+      int64_t next_stream_id;
+    } uni;
+
     struct {
       /* validation_start_ts is the timestamp when ECN validation is
          started.  It is UINT64_MAX if it has not started yet. */
@@ -441,10 +466,28 @@ struct ngtcp2_conn {
     ngtcp2_ccerr ccerr;

     struct {
-      /* pkt_num is the lowest incoming packet number of the packet
-         that server verified preferred address usage of client. */
-      int64_t pkt_num;
-    } preferred_addr;
+      /* unsent_max_streams is the maximum number of streams of peer
+         initiated bidirectional stream which the local endpoint can
+         accept.  This limit is not yet notified to the remote
+         endpoint. */
+      uint64_t unsent_max_streams;
+      /* max_streams is the maximum number of streams of peer
+         initiated bidirectional stream which the local endpoint can
+         accept. */
+      uint64_t max_streams;
+    } bidi;
+
+    struct {
+      /* unsent_max_streams is the maximum number of streams of peer
+         initiated unidirectional stream which the local endpoint can
+         accept.  This limit is not yet notified to the remote
+         endpoint. */
+      uint64_t unsent_max_streams;
+      /* max_streams is the maximum number of streams of peer
+         initiated unidirectional stream which the local endpoint can
+         accept. */
+      uint64_t max_streams;
+    } uni;
   } rx;

   struct {
@@ -467,23 +510,6 @@ struct ngtcp2_conn {
     /* transport_params is the local transport parameters.  It is used
        for Short packet only. */
     ngtcp2_transport_params transport_params;
-    struct {
-      /* max_streams is the maximum number of bidirectional streams which
-         the local endpoint can open. */
-      uint64_t max_streams;
-      /* next_stream_id is the bidirectional stream ID which the local
-         endpoint opens next. */
-      int64_t next_stream_id;
-    } bidi;
-
-    struct {
-      /* max_streams is the maximum number of unidirectional streams
-         which the local endpoint can open. */
-      uint64_t max_streams;
-      /* next_stream_id is the unidirectional stream ID which the
-         local endpoint opens next. */
-      int64_t next_stream_id;
-    } uni;
   } local;

   struct {
@@ -494,33 +520,18 @@ struct ngtcp2_conn {
        during handshake.  It is copied to transport_params when 1RTT
        key is available. */
     ngtcp2_transport_params *pending_transport_params;
-    struct {
-      ngtcp2_idtr idtr;
-      /* unsent_max_streams is the maximum number of streams of peer
-         initiated bidirectional stream which the local endpoint can
-         accept.  This limit is not yet notified to the remote
-         endpoint. */
-      uint64_t unsent_max_streams;
-      /* max_streams is the maximum number of streams of peer
-         initiated bidirectional stream which the local endpoint can
-         accept. */
-      uint64_t max_streams;
-    } bidi;
-
-    struct {
-      ngtcp2_idtr idtr;
-      /* unsent_max_streams is the maximum number of streams of peer
-         initiated unidirectional stream which the local endpoint can
-         accept.  This limit is not yet notified to the remote
-         endpoint. */
-      uint64_t unsent_max_streams;
-      /* max_streams is the maximum number of streams of peer
-         initiated unidirectional stream which the local endpoint can
-         accept. */
-      uint64_t max_streams;
-    } uni;
   } remote;

+  struct {
+    /* idtr tracks the stream ID usage. */
+    ngtcp2_idtr idtr;
+  } bidi;
+
+  struct {
+    /* idtr tracks the stream ID usage. */
+    ngtcp2_idtr idtr;
+  } uni;
+
   struct {
     struct {
       /* new_tx_ckm is a new sender 1RTT key which has not been
@@ -786,18 +797,19 @@ int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,

 /*
  * ngtcp2_conn_tx_strmq_top returns the ngtcp2_strm which sits on the
- * top of queue.  tx_strmq must not be empty.
+ * top of queue.  |conn|->tx.strmq must not be empty.
  */
 ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn);

 /*
  * ngtcp2_conn_tx_strmq_pop pops the ngtcp2_strm from the queue.
- * tx_strmq must not be empty.
+ * |conn|->tx.strmq must not be empty.
  */
 void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn);

 /*
- * ngtcp2_conn_tx_strmq_push pushes |strm| into tx_strmq.
+ * ngtcp2_conn_tx_strmq_push pushes |strm| into |conn|->tx.strmq.
+ * |strm|->cycle must be set before calling this function.
  *
  *  This function returns 0 if it succeeds, or one of the following
  * negative error codes:
@@ -807,6 +819,18 @@ void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn);
  */
 int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm);

+/*
+ * ngtcp2_conn_tx_strmq_push_if_not pushes |strm| into
+ * |conn|->tx.strmq if |strm| is not pushed yet.
+ *
+ *  This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_conn_tx_strmq_push_if_not(ngtcp2_conn *conn, ngtcp2_strm *strm);
+
 /*
  * ngtcp2_conn_internal_expiry returns the minimum expiry time among
  * all timers in |conn|.
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_crypto.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_crypto.h
index c1e786b5fc7..ae166ed7fb6 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_crypto.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_crypto.h
@@ -54,7 +54,9 @@ typedef struct ngtcp2_crypto_km {
   /* pkt_num is a packet number of a packet which uses this keying
      material.  For encryption key, it is the lowest packet number of
      a packet.  For decryption key, it is the lowest packet number of
-     a packet which can be decrypted with this keying material. */
+     a packet which can be decrypted with this keying material, or
+     INT64_MAX if the local endpoint initiated key update, and no
+     packet has been decrypted with this keying material yet. */
   int64_t pkt_num;
   /* use_count is the number of encryption applied with this key. */
   uint64_t use_count;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c
index 0360ba82cbd..71fc7caf76e 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c
@@ -174,39 +174,32 @@ int ngtcp2_dcidtr_verify_stateless_reset(
   return NGTCP2_ERR_INVALID_ARGUMENT;
 }

-static int verify_token_uniqueness(const ngtcp2_ringbuf *rb, int *pfound,
-                                   uint64_t seq, const ngtcp2_cid *cid,
-                                   const ngtcp2_stateless_reset_token *token) {
+static int verify_cid_uniqueness(const ngtcp2_ringbuf *rb,
+                                 const ngtcp2_cid *cid) {
   const ngtcp2_dcid *dcid;
   size_t i, len = ngtcp2_ringbuf_len(rb);
-  int rv;

   for (i = 0; i < len; ++i) {
     dcid = ngtcp2_ringbuf_get(rb, i);
-    rv = ngtcp2_dcid_verify_uniqueness(dcid, seq, cid, token);
-    if (rv != 0) {
-      return NGTCP2_ERR_PROTO;
-    }

     if (ngtcp2_cid_eq(&dcid->cid, cid)) {
-      *pfound = 1;
+      return NGTCP2_ERR_PROTO;
     }
   }

   return 0;
 }

-int ngtcp2_dcidtr_verify_token_uniqueness(
-  const ngtcp2_dcidtr *dtr, int *pfound, uint64_t seq, const ngtcp2_cid *cid,
-  const ngtcp2_stateless_reset_token *token) {
+int ngtcp2_dcidtr_verify_cid_uniqueness(const ngtcp2_dcidtr *dtr,
+                                        const ngtcp2_cid *cid) {
   int rv;

-  rv = verify_token_uniqueness(&dtr->bound.rb, pfound, seq, cid, token);
+  rv = verify_cid_uniqueness(&dtr->bound.rb, cid);
   if (rv != 0) {
     return rv;
   }

-  return verify_token_uniqueness(&dtr->unused.rb, pfound, seq, cid, token);
+  return verify_cid_uniqueness(&dtr->unused.rb, cid);
 }

 static void remove_dcid_at(ngtcp2_ringbuf *rb, size_t at) {
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h
index 945ed12bae1..9dc97457df2 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h
@@ -159,30 +159,17 @@ int ngtcp2_dcidtr_verify_stateless_reset(
   const ngtcp2_stateless_reset_token *token);

 /*
- * ngtcp2_dcidtr_verify_token_uniqueness verifies that the uniqueness
- * of the combination of |seq|, |cid|, and |token| against the exiting
- * Destination Connection IDs.  That is:
- *
- * - If they do not share the same seq, then their Connection IDs must
- *   be different.
- *
- * - If they share the same seq, then their Connection IDs and tokens
- *   must be the same.
- *
- * If this function succeeds, and there is Destination Connection ID
- * which shares |seq|, |cid|, and |token|, |*pfound| is set to
- * nonzero.
+ * ngtcp2_dcidtr_verify_cid_uniqueness verifies that |dtr| does not
+ * contain |cid|.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
  * NGTCP2_ERR_PROTO
- *     The given combination of values does not satisfy the above
- *     conditions.
+ *     |dtr| contains |cid|.
  */
-int ngtcp2_dcidtr_verify_token_uniqueness(
-  const ngtcp2_dcidtr *dtr, int *pfound, uint64_t seq, const ngtcp2_cid *cid,
-  const ngtcp2_stateless_reset_token *token);
+int ngtcp2_dcidtr_verify_cid_uniqueness(const ngtcp2_dcidtr *dtr,
+                                        const ngtcp2_cid *cid);

 /*
  * ngtcp2_dcidtr_retire_inactive_dcid_prior_to retires inactive
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c
index 51a9d70d13c..cc81e3dd775 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c
@@ -284,7 +284,7 @@ static void log_fr_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
                                 const char *dir) {
   ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, NGTCP2_LOG_PKT(dir, hd),
                        " DATA_BLOCKED(0x", hex(fr->type),
-                       ") offset=", fr->offset);
+                       ") max_data=", fr->max_data);
 }

 static void log_fr_stream_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
@@ -292,7 +292,8 @@ static void log_fr_stream_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
                                        const char *dir) {
   ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, NGTCP2_LOG_PKT(dir, hd),
                        " STREAM_DATA_BLOCKED(0x", hex(fr->type), ") id=0x",
-                       hex(fr->stream_id), " offset=", fr->offset);
+                       hex(fr->stream_id),
+                       " max_stream_data=", fr->max_stream_data);
 }

 static void log_fr_streams_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c
index 3ad4b9f1ef0..ecc7cfa384c 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c
@@ -30,6 +30,7 @@
 #include <stdio.h>

 #include "ngtcp2_conv.h"
+#include "ngtcp2_macro.h"

 #define NGTCP2_INITIAL_HASHBITS 4

@@ -74,18 +75,19 @@ int ngtcp2_map_each(const ngtcp2_map *map, int (*func)(void *data, void *ptr),
   return 0;
 }

-/* Hasher from
-   https://github.com/rust-lang/rustc-hash/blob/dc5c33f1283de2da64d8d7a06401d91aded03ad4/src/lib.rs
-   to maximize the output's sensitivity to all input bits. */
-#define NGTCP2_MAP_HASHER 0xF1357AEA2E62A9C5ULL
-/* 64-bit Fibonacci hashing constant, Golden Ratio constant, to get
-   the high bits with the good distribution. */
-#define NGTCP2_MAP_FIBO 0x9E3779B97F4A7C15ULL
+/* 64-bit finalizer (fmix64) from MurmurHash3 to maximize the output's
+   sensitivity to all input bits. */
+#define NGTCP2_MAP_HASHER1 0xFF51AFD7ED558CCDULL
+#define NGTCP2_MAP_HASHER2 0xC4CEB9FE1A85EC53ULL

 static size_t map_index(const ngtcp2_map *map, ngtcp2_map_key_type key) {
-  key += map->seed;
-  key *= NGTCP2_MAP_HASHER;
-  return (size_t)((key * NGTCP2_MAP_FIBO) >> (64 - map->hashbits));
+  key ^= map->seed;
+  key ^= key >> 33;
+  key *= NGTCP2_MAP_HASHER1;
+  key ^= key >> 33;
+  key *= NGTCP2_MAP_HASHER2;
+  key ^= key >> 33;
+  return (size_t)(key >> (64 - map->hashbits));
 }

 #ifndef WIN32
@@ -107,17 +109,17 @@ void ngtcp2_map_print_distance(const ngtcp2_map *map) {
     }

     idx = map_index(map, map->keys[i]);
-    fprintf(stderr, "@%zu key=%" PRIu64 " base=%zu distance=%u\n", i,
+    fprintf(stderr, "@%zu key=%" PRIu64 " base=%zu distance=%zu\n", i,
             map->keys[i], idx, map->psl[i] - 1);
   }
 }
 #endif /* !defined(WIN32) */

 static void map_set_entry(ngtcp2_map *map, size_t idx, ngtcp2_map_key_type key,
-                          void *data, size_t psl) {
+                          size_t psl, void *data) {
   map->keys[idx] = key;
+  map->psl[idx] = psl;
   map->data[idx] = data;
-  map->psl[idx] = (uint8_t)psl;
 }

 #define NGTCP2_SWAP(TYPE, A, B)                                                \
@@ -129,15 +131,15 @@ static void map_set_entry(ngtcp2_map *map, size_t idx, ngtcp2_map_key_type key,
   } while (0)

 /*
- * map_insert inserts |key| and |data| to |map|, and returns the index
- * where the pair is stored if it succeeds.  Otherwise, it returns one
- * of the following negative error codes:
+ * map_insert inserts |key| and |data| to |map|.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
  *
  * NGTCP2_ERR_INVALID_ARGUMENT
  *     The another data associated to |key| is already present.
  */
-static ngtcp2_ssize map_insert(ngtcp2_map *map, ngtcp2_map_key_type key,
-                               void *data) {
+static int map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) {
   size_t idx = map_index(map, key);
   size_t mask = ((size_t)1 << map->hashbits) - 1;
   size_t psl = 1;
@@ -147,16 +149,16 @@ static ngtcp2_ssize map_insert(ngtcp2_map *map, ngtcp2_map_key_type key,
     kpsl = map->psl[idx];

     if (kpsl == 0) {
-      map_set_entry(map, idx, key, data, psl);
+      map_set_entry(map, idx, key, psl, data);
       ++map->size;

-      return (ngtcp2_ssize)idx;
+      return 0;
     }

     if (psl > kpsl) {
       NGTCP2_SWAP(ngtcp2_map_key_type, &key, &map->keys[idx]);
+      NGTCP2_SWAP(size_t, &psl, &map->psl[idx]);
       NGTCP2_SWAP(void *, &data, &map->data[idx]);
-      NGTCP2_SWAP(uint8_t, &psl, &map->psl[idx]);
     } else if (map->keys[idx] == key) {
       /* This check ensures that no duplicate keys are inserted.  But
          it is just a waste after first swap or if this function is
@@ -178,14 +180,14 @@ static ngtcp2_ssize map_insert(ngtcp2_map *map, ngtcp2_map_key_type key,
 static int map_resize(ngtcp2_map *map, size_t new_hashbits) {
   size_t i;
   size_t tablelen;
-  ngtcp2_ssize idx;
   ngtcp2_map new_map = {
     .mem = map->mem,
     .seed = map->seed,
     .hashbits = new_hashbits,
   };
   void *buf;
-  (void)idx;
+  int rv;
+  (void)rv;

   if (new_hashbits > NGTCP2_MAP_MAX_HASHBITS) {
     return NGTCP2_ERR_NOMEM;
@@ -194,16 +196,16 @@ static int map_resize(ngtcp2_map *map, size_t new_hashbits) {
   tablelen = (size_t)1 << new_hashbits;

   buf = ngtcp2_mem_calloc(map->mem, tablelen,
-                          sizeof(ngtcp2_map_key_type) + sizeof(void *) +
-                            sizeof(uint8_t));
-  if (buf == NULL) {
+                          sizeof(ngtcp2_map_key_type) + sizeof(size_t) +
+                            sizeof(void *));
+  if (!buf) {
     return NGTCP2_ERR_NOMEM;
   }

   new_map.keys = buf;
-  new_map.data =
-    (void *)((uint8_t *)new_map.keys + tablelen * sizeof(ngtcp2_map_key_type));
-  new_map.psl = (uint8_t *)new_map.data + tablelen * sizeof(void *);
+  new_map.psl = (size_t *)(void *)((uint8_t *)new_map.keys +
+                                   tablelen * sizeof(ngtcp2_map_key_type));
+  new_map.data = (void *)((uint8_t *)new_map.psl + tablelen * sizeof(size_t));

   if (map->size) {
     tablelen = (size_t)1 << map->hashbits;
@@ -213,31 +215,26 @@ static int map_resize(ngtcp2_map *map, size_t new_hashbits) {
         continue;
       }

-      idx = map_insert(&new_map, map->keys[i], map->data[i]);
+      rv = map_insert(&new_map, map->keys[i], map->data[i]);

       /* map_insert must not fail because all keys are unique during
          resize. */
-      assert(idx >= 0);
+      assert(0 == rv);
     }
   }

   ngtcp2_mem_free(map->mem, map->keys);
   map->keys = new_map.keys;
-  map->data = new_map.data;
   map->psl = new_map.psl;
+  map->data = new_map.data;
   map->hashbits = new_hashbits;

   return 0;
 }

-/* NGTCP2_MAX_PSL_RESIZE_THRESH is the maximum psl threshold.  If
-   reached, resize the table. */
-#define NGTCP2_MAX_PSL_RESIZE_THRESH 128
-
 int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) {
   int rv;
   size_t tablelen;
-  ngtcp2_ssize idx;

   assert(data);

@@ -254,27 +251,9 @@ int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) {
     if (rv != 0) {
       return rv;
     }
-
-    idx = map_insert(map, key, data);
-    if (idx < 0) {
-      return (int)idx;
-    }
-
-    return 0;
-  }
-
-  idx = map_insert(map, key, data);
-  if (idx < 0) {
-    return (int)idx;
-  }
-
-  /* Resize if psl reaches really large value which is almost
-     improbable, but just in case. */
-  if (map->psl[idx] - 1 < NGTCP2_MAX_PSL_RESIZE_THRESH) {
-    return 0;
   }

-  return map_resize(map, map->hashbits + 1);
+  return map_insert(map, key, data);
 }

 void *ngtcp2_map_find(const ngtcp2_map *map, ngtcp2_map_key_type key) {
@@ -332,7 +311,7 @@ int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key) {
           break;
         }

-        map_set_entry(map, dest, map->keys[idx], map->data[idx], kpsl - 1);
+        map_set_entry(map, dest, map->keys[idx], kpsl - 1, map->data[idx]);

         dest = idx;

diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.h
index 1afe3167e65..524e02e3930 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.h
@@ -40,11 +40,11 @@ typedef uint64_t ngtcp2_map_key_type;

 typedef struct ngtcp2_map {
   ngtcp2_map_key_type *keys;
-  void **data;
   /* psl is the Probe Sequence Length.  0 has special meaning that the
      element is not stored at i-th position if psl[i] == 0.  Because
      of this, the actual psl value is psl[i] - 1 if psl[i] > 0. */
-  uint8_t *psl;
+  size_t *psl;
+  void **data;
   const ngtcp2_mem *mem;
   uint64_t seed;
   size_t size;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c
index d146145843b..06595fe6be8 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c
@@ -70,7 +70,9 @@ int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, const uint8_t *data,
   size_t dcidlen, scidlen;
   int supported_version;

-  assert(datalen);
+  if (datalen == 0) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }

   if (data[0] & NGTCP2_HEADER_FORM_BIT) {
     /* 1 byte (Header Form, Fixed Bit, Long Packet Type, Type-Specific bits)
@@ -307,7 +309,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,

     ngtcp2_get_uvarint(&vi, p);
 #if SIZE_MAX < UINT64_MAX
-    if (vi > SIZE_MAX) {
+    if (vi > SIZE_MAX / 2) {
       return NGTCP2_ERR_INVALID_ARGUMENT;
     }
 #endif /* SIZE_MAX < UINT64_MAX */
@@ -1087,7 +1089,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest,
   }

   dest->type = NGTCP2_FRAME_DATA_BLOCKED;
-  p = ngtcp2_get_uvarint(&dest->offset, p);
+  p = ngtcp2_get_uvarint(&dest->max_data, p);

   assert((size_t)(p - payload) == len);

@@ -1126,7 +1128,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_stream_data_blocked_frame(

   dest->type = NGTCP2_FRAME_STREAM_DATA_BLOCKED;
   p = ngtcp2_get_varint(&dest->stream_id, p);
-  p = ngtcp2_get_uvarint(&dest->offset, p);
+  p = ngtcp2_get_uvarint(&dest->max_stream_data, p);

   assert((size_t)(p - payload) == len);

@@ -1820,7 +1822,7 @@ ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen,
 ngtcp2_ssize
 ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen,
                                      const ngtcp2_data_blocked *fr) {
-  size_t len = 1 + ngtcp2_put_uvarintlen(fr->offset);
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_data);
   uint8_t *p;

   if (outlen < len) {
@@ -1830,7 +1832,7 @@ ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen,
   p = out;

   *p++ = NGTCP2_FRAME_DATA_BLOCKED;
-  p = ngtcp2_put_uvarint(p, fr->offset);
+  p = ngtcp2_put_uvarint(p, fr->max_data);

   assert((size_t)(p - out) == len);

@@ -1840,7 +1842,7 @@ ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen,
 ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame(
   uint8_t *out, size_t outlen, const ngtcp2_stream_data_blocked *fr) {
   size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) +
-               ngtcp2_put_uvarintlen(fr->offset);
+               ngtcp2_put_uvarintlen(fr->max_stream_data);
   uint8_t *p;

   if (outlen < len) {
@@ -1851,7 +1853,7 @@ ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame(

   *p++ = NGTCP2_FRAME_STREAM_DATA_BLOCKED;
   p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id);
-  p = ngtcp2_put_uvarint(p, fr->offset);
+  p = ngtcp2_put_uvarint(p, fr->max_stream_data);

   assert((size_t)(p - out) == len);

@@ -2193,8 +2195,7 @@ int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num,
   uint64_t mask = win - 1;
   uint64_t cand = (expected & ~mask) | (uint64_t)pkt_num;

-  if (cand + hwin <= expected) {
-    assert(cand <= NGTCP2_MAX_VARINT - win);
+  if (cand + hwin <= expected && cand <= NGTCP2_MAX_VARINT - win) {
     return (int64_t)(cand + win);
   }

@@ -2439,11 +2440,6 @@ size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset,

   left -= n;

-  if (left > 8 + 1073741823 && len > 1073741823) {
-    len = ngtcp2_min(len, 4611686018427387903UL);
-    return (size_t)ngtcp2_min(len, (uint64_t)(left - 8));
-  }
-
   if (left > 4 + 16383 && len > 16383) {
     len = ngtcp2_min(len, 1073741823);
     return (size_t)ngtcp2_min(len, (uint64_t)(left - 4));
@@ -2469,13 +2465,6 @@ size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left) {

   left -= n;

-  if (left > 8 + 1073741823 && len > 1073741823) {
-#if SIZE_MAX == UINT64_MAX
-    len = ngtcp2_min(len, 4611686018427387903UL);
-#endif /* SIZE_MAX == UINT64_MAX */
-    return ngtcp2_min(len, left - 8);
-  }
-
   if (left > 4 + 16383 && len > 16383) {
     len = ngtcp2_min(len, 1073741823);
     return ngtcp2_min(len, left - 4);
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h
index b6c818d491a..68f81d75521 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h
@@ -281,13 +281,13 @@ typedef struct ngtcp2_ping {

 typedef struct ngtcp2_data_blocked {
   uint64_t type;
-  uint64_t offset;
+  uint64_t max_data;
 } ngtcp2_data_blocked;

 typedef struct ngtcp2_stream_data_blocked {
   uint64_t type;
   int64_t stream_id;
-  uint64_t offset;
+  uint64_t max_stream_data;
 } ngtcp2_stream_data_blocked;

 typedef struct ngtcp2_streams_blocked {
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.c
index 81bc3296de8..0fb723d8ceb 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.c
@@ -86,7 +86,12 @@ int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) {

   buf->last += rv;

-  if (ngtcp2_buf_cap(buf) < ppe_sample_offset(ppe) + NGTCP2_HP_SAMPLELEN) {
+  /* The space for header protection sample only guarantees 4 -
+     hd->pkt_numlen bytes of payload, which is 0 if packet number is
+     encoded in 4 bytes.  A packet must contain at least one frame, so
+     make sure that at least 1 byte of payload can be written. */
+  if (ngtcp2_buf_cap(buf) < ppe_sample_offset(ppe) + NGTCP2_HP_SAMPLELEN ||
+      ngtcp2_ppe_left(ppe) == 0) {
     return NGTCP2_ERR_NOBUF;
   }

diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.h
index ba9edc698ad..5ba61fffb6f 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ppe.h
@@ -70,7 +70,8 @@ void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen,
                      size_t dgram_offset, ngtcp2_crypto_cc *cc);

 /*
- * ngtcp2_ppe_encode_hd encodes |hd|.
+ * ngtcp2_ppe_encode_hd encodes |hd|.  If it succeeds, it is
+ * guaranteed that at least 1 byte of payload can be written.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c
index f9abba46daf..eae6018bb85 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c
@@ -117,7 +117,12 @@ int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags,
 void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts) {
   ngtcp2_pv_entry *ent;

-  if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) {
+  /* Do not start new round until at least one PATH_CHALLENGE is sent
+     in the current round.  It might not be sent (e.g., server is
+     blocked by anti-amplification limit), and the last entry remains
+     expired. */
+  if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0 ||
+      pv->probe_pkt_left == NGTCP2_PV_NUM_PROBE_PKT) {
     return;
   }

@@ -158,13 +163,21 @@ int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts) {
 ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv) {
   ngtcp2_pv_entry *ent;

-  if ((pv->flags & NGTCP2_PV_FLAG_CANCEL_TIMER) ||
-      ngtcp2_ringbuf_len(&pv->ents.rb) == 0) {
+  if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) {
     return UINT64_MAX;
   }

   ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1);

+  if (pv->flags & NGTCP2_PV_FLAG_CANCEL_TIMER) {
+    /* The timer of the last entry has expired, but a new
+       PATH_CHALLENGE might never be sent (e.g., server is blocked by
+       anti-amplification limit), which leaves no timer armed.  Keep
+       the timer for the timeout of path validation armed, which is
+       the value ngtcp2_pv_validation_timed_out compares with. */
+    return ngtcp2_max(pv->started_ts + pv->timeout, ent->expiry);
+  }
+
   return ent->expiry;
 }

diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h
index 28bdf722b20..2d3c16a93b6 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h
@@ -69,19 +69,24 @@ void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent,
 /* NGTCP2_PV_FLAG_DONT_CARE indicates that the outcome of path
    validation should be ignored entirely. */
 #define NGTCP2_PV_FLAG_DONT_CARE 0x01U
-/* NGTCP2_PV_FLAG_CANCEL_TIMER indicates that the expiry timer is
-   cancelled. */
+/* NGTCP2_PV_FLAG_CANCEL_TIMER indicates that the expiry timer of the
+   last ngtcp2_pv_entry is cancelled.  The timer for the timeout of
+   path validation itself is still armed. */
 #define NGTCP2_PV_FLAG_CANCEL_TIMER 0x02U
 /* NGTCP2_PV_FLAG_FALLBACK_PRESENT indicates that a fallback
    Destination Connection ID and PTO are available in ngtcp2_pv.  If
    path validation fails, then fallback to them.  If path validation
    succeeds, the fallback Destination Connection ID is retired if it
    is not zero length, and does not equal to the current Destination
-   Connection ID. */
+   Connection ID, and NGTCP2_PV_FLAG_DONT_RETIRE_FALLBACK is not
+   set. */
 #define NGTCP2_PV_FLAG_FALLBACK_PRESENT 0x04U
 /* NGTCP2_PV_FLAG_PREFERRED_ADDR indicates that client is migrating to
    server's preferred address.  This flag is only used by client. */
 #define NGTCP2_PV_FLAG_PREFERRED_ADDR 0x10U
+/* NGTCP2_PV_FLAG_DONT_RETIRE_FALLBACK prevents the fallback
+   Destination Connection ID from being retired. */
+#define NGTCP2_PV_FLAG_DONT_RETIRE_FALLBACK 0x20U

 typedef struct ngtcp2_pv ngtcp2_pv;

@@ -170,6 +175,9 @@ int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags,

 /*
  * ngtcp2_pv_handle_entry_expiry checks expiry of existing entries.
+ * If the last entry has expired, it starts new round.  Calling this
+ * function again has no effect until new entry is added by
+ * ngtcp2_pv_add_entry.
  */
 void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts);

@@ -191,7 +199,10 @@ int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts);
 ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv);

 /*
- * ngtcp2_pv_cancel_expired_timer cancels the expired timer.
+ * ngtcp2_pv_cancel_expired_timer cancels the expired timer.  It does
+ * not cancel the timer for the timeout of path validation.  The
+ * caller must check it with ngtcp2_pv_validation_timed_out before
+ * calling this function.
  */
 void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts);

diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c
index 9f233b6cd83..6e85793f3bb 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c
@@ -488,7 +488,7 @@ static uint8_t *write_data_blocked_frame(uint8_t *p,
 #define NGTCP2_QLOG_DATA_BLOCKED_FRAME_OVERHEAD 57

   p = write_verbatim(p, "{\"frame_type\":\"data_blocked\",");
-  p = write_pair_number(p, "limit", fr->offset);
+  p = write_pair_number(p, "limit", fr->max_data);
   *p++ = '}';

   return p;
@@ -505,7 +505,7 @@ write_stream_data_blocked_frame(uint8_t *p,
   p = write_verbatim(p, "{\"frame_type\":\"stream_data_blocked\",");
   p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id);
   *p++ = ',';
-  p = write_pair_number(p, "limit", fr->offset);
+  p = write_pair_number(p, "limit", fr->max_stream_data);
   *p++ = '}';

   return p;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.c
index d13ed9a14aa..46a1b8f6902 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.c
@@ -31,6 +31,8 @@

 void ngtcp2_ratelim_init(ngtcp2_ratelim *rlim, uint64_t burst, uint64_t rate,
                          ngtcp2_tstamp ts) {
+  burst = ngtcp2_min(burst, NGTCP2_RATELIM_MAX_BURST);
+
   *rlim = (ngtcp2_ratelim){
     .burst = burst,
     .rate = rate,
@@ -52,19 +54,16 @@ static void ratelim_update(ngtcp2_ratelim *rlim, ngtcp2_tstamp ts) {
   d = ts - rlim->ts;
   rlim->ts = ts;

-  if (rlim->rate > (UINT64_MAX - rlim->carry) / d) {
-    gain = UINT64_MAX;
-  } else {
+  if (rlim->rate <= (UINT64_MAX - rlim->carry) / d) {
     gain = rlim->rate * d + rlim->carry;
-  }
+    gps = gain / NGTCP2_SECONDS;

-  gps = gain / NGTCP2_SECONDS;
+    if (gps < rlim->burst && rlim->tokens < rlim->burst - gps) {
+      rlim->tokens += gps;
+      rlim->carry = gain % NGTCP2_SECONDS;

-  if (gps < rlim->burst && rlim->tokens < rlim->burst - gps) {
-    rlim->tokens += gps;
-    rlim->carry = gain % NGTCP2_SECONDS;
-
-    return;
+      return;
+    }
   }

   rlim->tokens = rlim->burst;
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.h
index 231f5d1b018..f85cd6df845 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ratelim.h
@@ -32,6 +32,9 @@

 #include <ngtcp2/ngtcp2.h>

+/* NGTCP2_RATELIM_MAX_BURST is the maximum value of the burst. */
+#define NGTCP2_RATELIM_MAX_BURST (UINT64_MAX / NGTCP2_SECONDS)
+
 typedef struct ngtcp2_ratelim {
   /* burst is the maximum number of tokens. */
   uint64_t burst;
@@ -47,8 +50,8 @@ typedef struct ngtcp2_ratelim {
   ngtcp2_tstamp ts;
 } ngtcp2_ratelim;

-/* ngtcp2_ratelim_init initializes |rlim| with the given
-   parameters. */
+/* ngtcp2_ratelim_init initializes |rlim| with the given parameters.
+   |burst| is clamped to NGTCP2_RATELIM_MAX_BURST. */
 void ngtcp2_ratelim_init(ngtcp2_ratelim *rlim, uint64_t burst, uint64_t rate,
                          ngtcp2_tstamp ts);

diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c
index 56b0d81fc13..f0a9aa4f5f0 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c
@@ -62,7 +62,6 @@ int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem) {

   rob->chunk = chunk;
   rob->mem = mem;
-  rob->discard_data = 0;

   return 0;
 }
@@ -96,10 +95,6 @@ static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data,
   const ngtcp2_range *r;
   uint64_t chunk_offset;

-  if (rob->discard_data) {
-    return 0;
-  }
-
   for (it = ngtcp2_ksl_lower_bound_search(&rob->dataksl, &range,
                                           ngtcp2_ksl_range_exclusive_search);
        len; ngtcp2_ksl_it_next(&it)) {
@@ -235,10 +230,6 @@ void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) {
     ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g);
   }

-  if (rob->discard_data) {
-    return;
-  }
-
   it = ngtcp2_ksl_begin(&rob->dataksl);

   for (; !ngtcp2_ksl_it_end(&it);) {
@@ -254,8 +245,8 @@ void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) {
   }
 }

-uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
-                            uint64_t offset) {
+size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
+                          uint64_t offset) {
   const ngtcp2_range *g;
   const ngtcp2_range *r;
   uint8_t *d;
@@ -272,12 +263,6 @@ uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
     return 0;
   }

-  if (rob->discard_data) {
-    *pdest = NULL;
-
-    return g->begin - offset;
-  }
-
   it = ngtcp2_ksl_begin(&rob->dataksl);
   r = ngtcp2_ksl_it_key(&it);
   d = ngtcp2_ksl_it_get(&it);
@@ -288,18 +273,14 @@ uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,

   *pdest = d + (offset - r->begin);

-  return ngtcp2_min(g->begin, r->end) - offset;
+  return (size_t)(ngtcp2_min(g->begin, r->end) - offset);
 }

-void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, uint64_t len) {
+void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len) {
   ngtcp2_ksl_it it;
   ngtcp2_range r;
   uint8_t *d;

-  if (rob->discard_data) {
-    return;
-  }
-
   it = ngtcp2_ksl_begin(&rob->dataksl);
   r = *(const ngtcp2_range *)ngtcp2_ksl_it_key(&it);
   d = ngtcp2_ksl_it_get(&it);
@@ -330,16 +311,3 @@ uint64_t ngtcp2_rob_first_gap_offset(const ngtcp2_rob *rob) {
 int ngtcp2_rob_data_buffered(const ngtcp2_rob *rob) {
   return ngtcp2_ksl_len(&rob->dataksl) != 0;
 }
-
-void ngtcp2_rob_discard_data(ngtcp2_rob *rob) {
-  ngtcp2_ksl_it it;
-
-  rob->discard_data = 1;
-
-  for (it = ngtcp2_ksl_begin(&rob->dataksl); !ngtcp2_ksl_it_end(&it);
-       ngtcp2_ksl_it_next(&it)) {
-    rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem);
-  }
-
-  ngtcp2_ksl_clear(&rob->dataksl);
-}
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h
index 6b616ea9b81..84194cae986 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h
@@ -50,9 +50,6 @@ typedef struct ngtcp2_rob {
   const ngtcp2_mem *mem;
   /* chunk is the size of each buffer in data field */
   size_t chunk;
-  /* discard_data, if nonzero, stops buffering data.  If it is
-     nonzero, ngtcp2_ksl_empty(&dataksl) always returns nonzero. */
-  int discard_data;
 } ngtcp2_rob;

 /*
@@ -76,10 +73,6 @@ void ngtcp2_rob_free(ngtcp2_rob *rob);
  * ngtcp2_rob_push adds new data pointed by |data| of length |datalen|
  * at the stream offset |offset|.
  *
- * If ngtcp2_rob_discard_data is called, this function does not buffer
- * data.  The return value is the number of bytes that would be
- * buffered if ngtcp2_rob_discard_data has not been called.
- *
  * This function returns the number of data newly buffered if it
  * succeeds, or one of the following negative error codes:
  *
@@ -102,11 +95,9 @@ void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset);
  * valid length of available data.  If no data is available, it
  * returns 0.  This function only returns the data before the first
  * gap.  It returns 0 even if data is available after the first gap.
- * If ngtcp2_rob_discard_data has been called, NULL is assigned to
- * |*pdest| if this function returns nonzero.
  */
-uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
-                            uint64_t offset);
+size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
+                          uint64_t offset);

 /*
  * ngtcp2_rob_pop clears data at stream offset |offset| of length
@@ -118,10 +109,8 @@ uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
  *
  * Caller should call this function from offset 0 in non-decreasing
  * order.
- *
- * ngtcp2_rob_pop is noop if ngtcp2_rob_discard_data has been called.
  */
-void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, uint64_t len);
+void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len);

 /*
  * ngtcp2_rob_first_gap_offset returns the offset to the first gap.
@@ -134,10 +123,4 @@ uint64_t ngtcp2_rob_first_gap_offset(const ngtcp2_rob *rob);
  */
 int ngtcp2_rob_data_buffered(const ngtcp2_rob *rob);

-/*
- * ngtcp2_rob_discard_data discards the buffered data, and stops
- * buffering data any further.
- */
-void ngtcp2_rob_discard_data(ngtcp2_rob *rob);
-
 #endif /* !defined(NGTCP2_ROB_H) */
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c
index e8d05a3fead..11336ce370b 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c
@@ -106,6 +106,7 @@ void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_rst *rst, ngtcp2_cc *cc,
   rtb->cc_bytes_in_flight = 0;
   rtb->num_lost_pkts = 0;
   rtb->num_lost_ignore_pkts = 0;
+  rtb->pto_reclaim_pkt_num = -1;
 }

 void ngtcp2_rtb_free(ngtcp2_rtb *rtb) {
@@ -247,7 +248,8 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
         fr->stream.offset + ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt);
       range = ngtcp2_range_intersect(&range, &gap);

-      if (ngtcp2_range_len(&range) == 0 && !fr->stream.fin &&
+      if (ngtcp2_range_len(&range) == 0 &&
+          (!fr->stream.fin || (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED)) &&
           /* 0 length STREAM frame with offset == 0 must be
              retransmitted if no non-empty data are sent to this
              stream, fin flag is not set, and no data in this stream
@@ -286,13 +288,9 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
         return rv;
       }

-      if (!ngtcp2_strm_is_tx_queued(strm)) {
-        strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
-
-        rv = ngtcp2_conn_tx_strmq_push(conn, strm);
-        if (rv != 0) {
-          return rv;
-        }
+      rv = ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
+      if (rv != 0) {
+        return rv;
       }

       ++num_reclaimed;
@@ -348,6 +346,7 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,

       rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem);
       if (rv != 0) {
+        ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem);
         return rv;
       }

@@ -374,6 +373,18 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
         continue;
       }

+      break;
+    case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+      if (fr->max_streams.max_streams < conn->rx.bidi.max_streams) {
+        continue;
+      }
+
+      break;
+    case NGTCP2_FRAME_MAX_STREAMS_UNI:
+      if (fr->max_streams.max_streams < conn->rx.uni.max_streams) {
+        continue;
+      }
+
       break;
     case NGTCP2_FRAME_MAX_STREAM_DATA:
       strm = ngtcp2_conn_find_stream(conn, fr->max_stream_data.stream_id);
@@ -382,6 +393,12 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
         continue;
       }

+      break;
+    case NGTCP2_FRAME_MAX_DATA:
+      if (fr->max_data.max_data < conn->rx.max_offset) {
+        continue;
+      }
+
       break;
     case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
       strm = ngtcp2_conn_find_stream(conn, fr->stream_data_blocked.stream_id);
@@ -390,6 +407,12 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
         continue;
       }

+      break;
+    case NGTCP2_FRAME_DATA_BLOCKED:
+      if (fr->data_blocked.max_data != conn->tx.max_offset) {
+        continue;
+      }
+
       break;
     }

@@ -402,6 +425,7 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,

     rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem);
     if (rv != 0) {
+      ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem);
       return rv;
     }

@@ -481,30 +505,31 @@ static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
     }
   }

+  /* DATAGRAM frame is not reclaimed on PTO, so its loss must be
+     notified even if the packet has already been reclaimed on
+     PTO. */
+  if (conn->callbacks.lost_datagram &&
+      (ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM)) {
+    rv = conn_process_lost_datagram(conn, ent);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
   if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) {
     ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC, "pkn=", ent->hd.pkt_num,
                      " has already been reclaimed on PTO");
     assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED));
     assert(UINT64_MAX == ent->lost_ts);
-  } else {
-    if (conn->callbacks.lost_datagram &&
-        (ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM)) {
-      rv = conn_process_lost_datagram(conn, ent);
-      if (rv != 0) {
-        return rv;
-      }
-    }
-
-    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) {
-      assert(ent->frc);
-      assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED));
-      assert(UINT64_MAX == ent->lost_ts);
+  } else if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) {
+    assert(ent->frc);
+    assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED));
+    assert(UINT64_MAX == ent->lost_ts);

-      reclaimed =
-        rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_ON_LOSS, conn, pktns, ent);
-      if (reclaimed < 0) {
-        return (int)reclaimed;
-      }
+    reclaimed =
+      rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_ON_LOSS, conn, pktns, ent);
+    if (reclaimed < 0) {
+      return (int)reclaimed;
     }
   }

@@ -798,7 +823,7 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
   size_t i;
   int rv;
   ngtcp2_ksl_it it;
-  size_t num_acked = 0;
+  size_t num_acked = 0, num_cc_acked = 0;
   int64_t pkt_num;
   ngtcp2_cc *cc = rtb->cc;
   ngtcp2_rtb_entry *acked_ent = NULL;
@@ -855,7 +880,10 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,

     ent = ngtcp2_ksl_it_get(&it);

-    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP) {
+    /* A packet cannot acknowledge a packet which was sent after it
+       had been received.  This could happen with a buffered
+       packet. */
+    if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP) || pkt_ts < ent->ts) {
       rv = NGTCP2_ERR_PROTO;
       goto fail;
     }
@@ -888,7 +916,7 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,

       ent = ngtcp2_ksl_it_get(&it);

-      if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP) {
+      if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP) || pkt_ts < ent->ts) {
         rv = NGTCP2_ERR_PROTO;
         goto fail;
       }
@@ -928,9 +956,11 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,

         rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts);

-        ++num_acked;
+        ++num_cc_acked;
       }

+      ++num_acked;
+
       acked_ent = ent->next;
       ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
                                     rtb->frc_objalloc, rtb->mem);
@@ -940,6 +970,7 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
     for (ent = acked_ent; ent; ent = acked_ent) {
       rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts);
       acked_ent = ent->next;
+      ++num_cc_acked;
       ++num_acked;
       ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
                                     rtb->frc_objalloc, rtb->mem);
@@ -951,9 +982,11 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
     rtb->cc->on_spurious_congestion(cc, cstat, ts);
   }

-  if (num_acked) {
+  if (num_cc_acked) {
     ngtcp2_rst_on_ack_recv(rtb->rst, cstat);
+  }

+  if (num_acked) {
     if (conn) {
       rv = rtb_detect_lost_pkt(rtb, &cc_ack, conn, pktns, cstat, ts);
       if (rv != 0) {
@@ -1189,7 +1222,7 @@ static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_cc_ack *cc_ack,
     }
   }

-  ngtcp2_rtb_remove_excessive_lost_pkt(rtb, (size_t)pkt_thres);
+  ngtcp2_rtb_remove_excessive_lost_pkt(rtb, (size_t)pkt_thres, cstat, ts);

   return 0;
 }
@@ -1205,7 +1238,34 @@ int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
                              conn, pktns, cstat, ts);
 }

-void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) {
+/*
+ * rtb_on_lost_pkt_removed is called when a lost packet |ent| is
+ * removed from |rtb| without being acknowledged.  It tells the
+ * congestion controller that the loss is not spurious.
+ */
+static void rtb_on_lost_pkt_removed(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                                    ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) {
+  ngtcp2_cc *cc = rtb->cc;
+
+  assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED);
+
+  --rtb->num_lost_pkts;
+
+  if (ent->flags &
+      (NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE | NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
+    --rtb->num_lost_ignore_pkts;
+
+    return;
+  }
+
+  if (cc->on_pkt_lost_confirmed) {
+    cc->on_pkt_lost_confirmed(cc, cstat, ts);
+  }
+}
+
+void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n,
+                                          ngtcp2_conn_stat *cstat,
+                                          ngtcp2_tstamp ts) {
   ngtcp2_ksl_it it = ngtcp2_ksl_end(&rtb->ents);
   ngtcp2_rtb_entry *ent;
   int rv;
@@ -1221,12 +1281,7 @@ void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) {
     ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC,
                      "removing stale lost pkn=", ent->hd.pkt_num);

-    --rtb->num_lost_pkts;
-
-    if (ent->flags &
-        (NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE | NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
-      --rtb->num_lost_ignore_pkts;
-    }
+    rtb_on_lost_pkt_removed(rtb, ent, cstat, ts);

     rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
     assert(0 == rv);
@@ -1237,6 +1292,7 @@ void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) {

 void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb,
                                         ngtcp2_duration timeout,
+                                        ngtcp2_conn_stat *cstat,
                                         ngtcp2_tstamp ts) {
   ngtcp2_ksl_it it;
   ngtcp2_rtb_entry *ent;
@@ -1264,12 +1320,7 @@ void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb,
     ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC,
                      "removing stale lost pkn=", ent->hd.pkt_num);

-    --rtb->num_lost_pkts;
-
-    if (ent->flags &
-        (NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE | NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
-      --rtb->num_lost_ignore_pkts;
-    }
+    rtb_on_lost_pkt_removed(rtb, ent, cstat, ts);

     rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
     assert(0 == rv);
@@ -1320,18 +1371,26 @@ static int rtb_reclaim_frame_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
         break;
       }

+      /* 0 length STREAM frame with offset == 0 must be retransmitted
+         only if no non-empty data are sent to this stream, and fin
+         flag is not set.  Otherwise, it shares the offset with the
+         first non-empty (or fin ) STREAM frame, and
+         ngtcp2_strm_streamfrq_push fails. */
+      if (sfr->offset == 0 && sfr->datacnt == 0 && !sfr->fin &&
+          (strm->tx.offset || (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR))) {
+        ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem);
+        break;
+      }
+
       rv = ngtcp2_strm_streamfrq_push(strm, frc);
       if (rv != 0) {
         ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem);
         return rv;
       }

-      if (!ngtcp2_strm_is_tx_queued(strm)) {
-        strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn);
-        rv = ngtcp2_conn_tx_strmq_push(conn, strm);
-        if (rv != 0) {
-          return rv;
-        }
+      rv = ngtcp2_conn_tx_strmq_push_if_not(conn, strm);
+      if (rv != 0) {
+        return rv;
       }

       break;
@@ -1400,11 +1459,6 @@ int ngtcp2_rtb_reclaim_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
       }
     }

-    /* We never send PING only probe packet because we should have
-       CRYPTO data or just nothing.  If we have nothing, then we do
-       not send probe packet. */
-    assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE));
-
     /* We never get ACK before Retry packet. */
     assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED));
     assert(0 == rtb->num_lost_pkts);
@@ -1486,11 +1540,13 @@ ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
   ngtcp2_ssize reclaimed;
   size_t atmost = num_pkts;

-  it = ngtcp2_ksl_end(&rtb->ents);
+  it = ngtcp2_ksl_lower_bound(&rtb->ents, &rtb->pto_reclaim_pkt_num);
   for (; !ngtcp2_ksl_it_begin(&it) && num_pkts;) {
     ngtcp2_ksl_it_prev(&it);
     ent = ngtcp2_ksl_it_get(&it);

+    rtb->pto_reclaim_pkt_num = ent->hd.pkt_num;
+
     if ((ent->flags & (NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED |
                        NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) ||
         !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE)) {
@@ -1512,15 +1568,22 @@ ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
     assert(rtb->num_retransmittable);
     --rtb->num_retransmittable;

+    /* If nothing is reclaimed, no packet is sent on behalf of ent.
+       Keep ent PTO eliciting so that loss detection timer stays armed,
+       and PING is sent as a probe.  Otherwise, if ent and the packets
+       reclaimed earlier are lost, nothing removes them from bytes in
+       flight. */
+    if (reclaimed == 0) {
+      continue;
+    }
+
     if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) {
       ent->flags &= (uint16_t)~NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING;
       assert(rtb->num_pto_eliciting);
       --rtb->num_pto_eliciting;
     }

-    if (reclaimed) {
-      --num_pkts;
-    }
+    --num_pkts;
   }

   return (ngtcp2_ssize)(atmost - num_pkts);
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h
index 664498be962..8789b68c84d 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h
@@ -195,6 +195,11 @@ typedef struct ngtcp2_rtb {
      excluded from lost byte count.  If only those packets are lost,
      congestion event is not triggered. */
   size_t num_lost_ignore_pkts;
+  /* pto_reclaim_pkt_num is the largest packet number among the
+     entries that ngtcp2_rtb_reclaim_on_pto has visited, or -1 if it
+     has not visited any entry yet.  The entries up to this packet
+     number have been reclaimed, or never will be. */
+  int64_t pto_reclaim_pkt_num;
 } ngtcp2_rtb;

 /*
@@ -243,6 +248,9 @@ ngtcp2_ksl_it ngtcp2_rtb_head(const ngtcp2_rtb *rtb);
  *     User callback failed
  * NGTCP2_ERR_NOMEM
  *     Out of memory
+ * NGTCP2_ERR_PROTO
+ *     |fr| acknowledges a skipped packet number, or a packet that was
+ *     sent after |pkt_ts|.
  */
 ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
                                  ngtcp2_conn_stat *cstat, ngtcp2_conn *conn,
@@ -264,6 +272,7 @@ int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
  */
 void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb,
                                         ngtcp2_duration timeout,
+                                        ngtcp2_conn_stat *cstat,
                                         ngtcp2_tstamp ts);

 /*
@@ -302,7 +311,9 @@ void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num);
  * ngtcp2_rtb_remove_excessive_lost_pkt ensures that the number of
  * lost packets is at most |n|.
  */
-void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n);
+void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n,
+                                          ngtcp2_conn_stat *cstat,
+                                          ngtcp2_tstamp ts);

 /*
  * ngtcp2_rtb_reclaim_on_pto reclaims up to |num_pkts| packets which
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_settings.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_settings.c
index 31c52b4d957..4062ddc0ab5 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_settings.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_settings.c
@@ -37,6 +37,7 @@ void ngtcp2_settings_default_versioned(int settings_version,

   switch (settings_version) {
   case NGTCP2_SETTINGS_VERSION:
+  case NGTCP2_SETTINGS_V4:
   case NGTCP2_SETTINGS_V3:
     settings->glitch_ratelim_burst = NGTCP2_DEFAULT_GLITCH_RATELIM_BURST;
     settings->glitch_ratelim_rate = NGTCP2_DEFAULT_GLITCH_RATELIM_RATE;
@@ -87,6 +88,8 @@ size_t ngtcp2_settingslen_version(int settings_version) {
   switch (settings_version) {
   case NGTCP2_SETTINGS_VERSION:
     return sizeof(settings);
+  case NGTCP2_SETTINGS_V4:
+    return offsetof(ngtcp2_settings, log_write) + sizeof(settings.log_write);
   case NGTCP2_SETTINGS_V3:
     return offsetof(ngtcp2_settings, glitch_ratelim_rate) +
            sizeof(settings.glitch_ratelim_rate);
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c
index aa01434bf16..b9e2cdfb199 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c
@@ -115,9 +115,21 @@ uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm) {
 }

 /* strm_rob_heavily_fragmented returns nonzero if the number of gaps
-   in |rob| exceeds the limit. */
-static int strm_rob_heavily_fragmented(const ngtcp2_rob *rob) {
-  return ngtcp2_ksl_len(&rob->gapksl) >= 4000;
+   in strm->rx.rob exceeds the limit.  A gap costs memory no matter
+   how small it is, while flow control only charges the remote
+   endpoint for the offset range that the reorder buffer covers.
+   Therefore, beyond a fixed allowance, each gap must be backed by
+   NGTCP2_MIN_STREAM_DATALEN bytes of that range. */
+static int strm_rob_heavily_fragmented(const ngtcp2_strm *strm) {
+  size_t ngap = ngtcp2_ksl_len(&strm->rx.rob->gapksl);
+  uint64_t rx_offset = ngtcp2_rob_first_gap_offset(strm->rx.rob);
+  uint64_t span;
+
+  assert(strm->rx.last_offset >= rx_offset);
+
+  span = strm->rx.last_offset - rx_offset;
+
+  return ngap >= 4000 || ngap > 128 + span / NGTCP2_MIN_STREAM_DATALEN;
 }

 ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
@@ -134,10 +146,6 @@ ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
     if (strm->rx.cont_offset) {
       ngtcp2_rob_remove_prefix(strm->rx.rob, strm->rx.cont_offset);
     }
-
-    if (strm->flags & NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING) {
-      ngtcp2_rob_discard_data(strm->rx.rob);
-    }
   }

   nwrite = ngtcp2_rob_push(strm->rx.rob, offset, data, datalen);
@@ -145,7 +153,7 @@ ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
     return nwrite;
   }

-  if (strm_rob_heavily_fragmented(strm->rx.rob)) {
+  if (strm_rob_heavily_fragmented(strm)) {
     return NGTCP2_ERR_INTERNAL;
   }

@@ -734,13 +742,12 @@ int ngtcp2_strm_require_retransmit_max_stream_data(

 int ngtcp2_strm_require_retransmit_stream_data_blocked(
   const ngtcp2_strm *strm, const ngtcp2_stream_data_blocked *fr) {
-  return fr->offset == strm->tx.max_offset &&
+  return fr->max_stream_data == strm->tx.max_offset &&
          !(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR);
 }

-uint64_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm,
-                                          uint64_t rx_offset) {
-  uint64_t datalen;
+size_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm, uint64_t rx_offset) {
+  size_t datalen;
   const uint8_t *data;
   uint64_t orig_rx_offset = rx_offset;

@@ -759,19 +766,5 @@ uint64_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm,
     rx_offset += datalen;
   }

-  return rx_offset - orig_rx_offset;
-}
-
-void ngtcp2_strm_stop_buffering_reordered_data(ngtcp2_strm *strm) {
-  if (strm->flags & NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING) {
-    return;
-  }
-
-  strm->flags |= NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING;
-
-  if (!strm->rx.rob) {
-    return;
-  }
-
-  ngtcp2_rob_discard_data(strm->rx.rob);
+  return (size_t)(rx_offset - orig_rx_offset);
 }
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h
index 0c47873c3db..6b1b7495d32 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h
@@ -89,9 +89,6 @@ typedef struct ngtcp2_frame_chain ngtcp2_frame_chain;
 /* NGTCP2_STRM_FLAG_ANY_SENT indicates that any STREAM frame,
    including empty one, has been sent. */
 #define NGTCP2_STRM_FLAG_ANY_SENT 0x1000U
-/* NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING is set when
-   ngtcp2_strm_stop_buffering_reordered_data is called. */
-#define NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING 0x2000U
 /* NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET is set when
    ngtcp2_strm.rx.app_error_code is set. */
 #define NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET 0x4000U
@@ -101,6 +98,14 @@ typedef struct ngtcp2_frame_chain ngtcp2_frame_chain;
 /* NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET is set when
    ngtcp2_strm.tx.stop_sending_app_error_code is set. */
 #define NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET 0x10000U
+/* NGTCP2_STRM_FLAG_SEND_STREAM_DATA_BLOCKED is set when
+   STREAM_DATA_BLOCKED and/or DATA_BLOCKED frame should be sent. */
+#define NGTCP2_STRM_FLAG_SEND_STREAM_DATA_BLOCKED 0x20000U
+/* NGTCP2_STRM_FLAG_RX_FIN_DELIVERED is set when all stream data up to
+   the final offset have been passed to an application via
+   ngtcp2_callbacks.recv_stream_data with
+   NGTCP2_STREAM_DATA_FLAG_FIN. */
+#define NGTCP2_STRM_FLAG_RX_FIN_DELIVERED 0x40000U

 typedef struct ngtcp2_strm ngtcp2_strm;

@@ -234,10 +239,6 @@ uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm);
 /*
  * ngtcp2_strm_recv_reordering handles reordered data.
  *
- * If ngtcp2_strm_stop_buffering_reordered_data has been called, this
- * function only records the range of the reordered data.  The actual
- * data is not buffered.
- *
  * It returns the number of bytes newly buffered if it succeeds, or
  * one of the following negative error codes:
  *
@@ -382,19 +383,10 @@ int ngtcp2_strm_require_retransmit_max_stream_data(
 int ngtcp2_strm_require_retransmit_stream_data_blocked(
   const ngtcp2_strm *strm, const ngtcp2_stream_data_blocked *fr);

-/*
- * ngtcp2_strm_discard_ordered_data discards the ordered data starting
- * at |rx_offset|.  It stops when it finds a gap, which means that a
- * portion of the data has not been received yet.  It returns the size
- * of the buffered bytes discarded.
- */
-uint64_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm,
-                                          uint64_t rx_offset);
-
-/*
- * ngtcp2_strm_stop_buffering_reordered_data discards the buffered
- * reordered data, and stops buffering data any further.
- */
-void ngtcp2_strm_stop_buffering_reordered_data(ngtcp2_strm *strm);
+/* ngtcp2_strm_discard_ordered_data discards the ordered data starting
+   at |rx_offset|.  It stops when it finds a gap, which means that a
+   portion of the data has not been received yet.  It returns the size
+   of the buffered bytes discarded. */
+size_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm, uint64_t rx_offset);

 #endif /* !defined(NGTCP2_STRM_H) */
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c
index f28c4f57b15..59765b1df2a 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c
@@ -586,10 +586,15 @@ int ngtcp2_transport_params_decode_versioned(int transport_params_version,
       if (decode_varint_param(&params->max_idle_timeout, &p, end) != 0) {
         return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
       }
+      /* Because we multiply the value by NGTCP2_MILLISECONDS, the
+         maximum max_idle_timeout is 18446744073709ms ~= 213504 days.
+         If it is larger than that, it is truncated. */
       if (params->max_idle_timeout > UINT64_MAX / NGTCP2_MILLISECONDS) {
-        params->max_idle_timeout = UINT64_MAX;
+        params->max_idle_timeout =
+          UINT64_MAX / NGTCP2_MILLISECONDS * NGTCP2_MILLISECONDS;
+      } else {
+        params->max_idle_timeout *= NGTCP2_MILLISECONDS;
       }
-      params->max_idle_timeout *= NGTCP2_MILLISECONDS;
       break;
     case NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE:
       if (decode_varint_param(&params->max_udp_payload_size, &p, end) != 0) {
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_unreachable.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_unreachable.h
index ca712386527..5ef25ad6a3a 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_unreachable.h
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_unreachable.h
@@ -43,10 +43,10 @@
 #ifdef _MSC_VER
 __declspec(noreturn)
 #endif /* defined(_MSC_VER) */
-    void ngtcp2_unreachable_fail(const char *file, int line, const char *func)
+void ngtcp2_unreachable_fail(const char *file, int line, const char *func)
 #ifndef _MSC_VER
-        __attribute__((noreturn))
+  __attribute__((noreturn))
 #endif /* !defined(_MSC_VER) */
-        ;
+  ;

 #endif /* !defined(NGTCP2_UNREACHABLE_H) */
diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c
index f94c6218d53..57b706685f6 100644
--- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c
+++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c
@@ -134,6 +134,8 @@ size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
     a = &dst[0];
     b = &src[0];

+    ++*pdstcnt;
+
     if (left < b->len) {
       a->len = left;
       a->base = b->base;
@@ -144,7 +146,6 @@ size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
     }

     *a = *b;
-    ++*pdstcnt;
     left -= b->len;
     i = 1;
   }
diff --git a/src/quic/session.cc b/src/quic/session.cc
index 1d06b1fdcc0..e0de45d13fd 100644
--- a/src/quic/session.cc
+++ b/src/quic/session.cc
@@ -1705,6 +1705,9 @@ struct Session::Impl final : public MemoryRetainer {
       on_receive_stream_stop_sending,
 #ifdef NGTCP2_CALLBACKS_V5
       nullptr,  // stream_close2
+#endif
+#ifdef NGTCP2_CALLBACKS_V6
+      nullptr,  // extend_max_data
 #endif
   };

@@ -1757,6 +1760,9 @@ struct Session::Impl final : public MemoryRetainer {
       on_receive_stream_stop_sending,
 #ifdef NGTCP2_CALLBACKS_V5
       nullptr,  // stream_close2
+#endif
+#ifdef NGTCP2_CALLBACKS_V6
+      nullptr,  // extend_max_data
 #endif
   };
 };
@@ -3707,8 +3713,9 @@ void Session::OnTimeout() {
   // on a freed object.
   BaseObjectPtr<Session> ref(this);
   HandleScope scope(env()->isolate());
-  int ret;
-  {
+  int ret = 0;
+  // Once we're draining, we don't want ngtcp to handle expired timeouts
+  if (!is_in_draining_period()) {
     NgTcp2CallbackScope callback_scope(this);
     ret = ngtcp2_conn_handle_expiry(*this, uv_hrtime());
   }
diff --git a/test/parallel/test-quic-callback-error-stream-onerror.mjs b/test/parallel/test-quic-callback-error-stream-onerror.mjs
index 0e8505cdb0e..5cc0edc6fe1 100644
--- a/test/parallel/test-quic-callback-error-stream-onerror.mjs
+++ b/test/parallel/test-quic-callback-error-stream-onerror.mjs
@@ -21,11 +21,18 @@ const serverDone = Promise.withResolvers();

 const serverEndpoint = await listen(mustCall((serverSession) => {
   serverSession.onstream = mustCall(async (stream) => {
+    // The client destroys its first stream with an error, which resets it.
+    if (stream.id === 0n) {
+      await assert.rejects(stream.closed, {
+        code: 'ERR_QUIC_APPLICATION_ERROR',
+      });
+      return;
+    }
     stream.writer.endSync();
     await stream.closed;
     serverSession.close();
     serverDone.resolve();
-  });
+  }, 2);
 }));

 const clientSession = await connect(serverEndpoint.address);